8#include <condition_variable>
30#if defined(__aarch64__) || defined(_M_ARM64)
33#if defined(__ARM_FEATURE_SVE)
35#elif defined(__x86_64__) || defined(_M_X64)
36 #include <immintrin.h>
62 return ch ==
'\n' || ch ==
'\r' || ch ==
' ' || ch ==
'\t';
93 while (position < data.size() && data[position] !=
'\n' && data[position] !=
'\r') {
99#if (defined(__GNUC__) || defined(__clang__)) && defined(__x86_64__)
100[[nodiscard]] __attribute__((target(
"avx2"))) inline
size_t
101fastx_line_end_avx2(std::string_view data,
size_t position) {
102 const __m256i newline = _mm256_set1_epi8(
'\n');
103 const __m256i carriage_return = _mm256_set1_epi8(
'\r');
104 while (data.size() - position >= 32) {
105 const __m256i bytes =
106 _mm256_loadu_si256(
reinterpret_cast<const __m256i*
>(data.data() + position));
107 const auto mask =
static_cast<uint32_t
>(_mm256_movemask_epi8(_mm256_or_si256(
108 _mm256_cmpeq_epi8(bytes, newline), _mm256_cmpeq_epi8(bytes, carriage_return)
111 return position +
static_cast<size_t>(__builtin_ctz(mask));
119#if (defined(__GNUC__) || defined(__clang__)) && defined(__x86_64__)
120[[nodiscard]] __attribute__((target(
"avx512f,avx512bw"))) inline
size_t
121fastx_line_end_avx512(std::string_view data,
size_t position) {
122 while (data.size() - position >= 64) {
123 auto const bytes = _mm512_loadu_si512(data.data() + position);
124 auto const mask = _mm512_cmpeq_epi8_mask(bytes, _mm512_set1_epi8(
'\n')) |
125 _mm512_cmpeq_epi8_mask(bytes, _mm512_set1_epi8(
'\r'));
126 if (mask != 0)
return position + std::countr_zero(mask);
133#if defined(__ARM_FEATURE_SVE)
135[[nodiscard]]
inline size_t fastx_line_end_sve(std::string_view data,
size_t position) {
136 while (position < data.size()) {
137 const svbool_t active = svwhilelt_b8(position, data.size());
138 const svuint8_t bytes =
139 svld1_u8(active,
reinterpret_cast<const uint8_t*
>(data.data() + position));
140 const svbool_t matches =
141 svorr_b_z(active, svcmpeq_n_u8(active, bytes,
'\n'), svcmpeq_n_u8(active, bytes,
'\r'));
142 if (svptest_any(active, matches)) {
143 return position + svcntp_b8(active, svbrkb_z(active, matches));
145 position += svcntb();
151#if defined(__aarch64__) || defined(_M_ARM64)
153[[nodiscard]]
inline size_t fastx_line_end_neon(std::string_view data,
size_t position) {
154 const uint8x16_t newline = vdupq_n_u8(
'\n');
155 const uint8x16_t carriage_return = vdupq_n_u8(
'\r');
156 while (data.size() - position >= 16) {
157 const uint8x16_t bytes = vld1q_u8(
reinterpret_cast<const uint8_t*
>(data.data() + position));
158 const uint8x16_t matches =
159 vorrq_u8(vceqq_u8(bytes, newline), vceqq_u8(bytes, carriage_return));
160 const uint64x2_t
words = vreinterpretq_u64_u8(matches);
161 const uint64_t
low = vgetq_lane_u64(
words, 0);
163 return position + (std::countr_zero(
low) >> 3);
165 const uint64_t
high = vgetq_lane_u64(
words, 1);
167 return position + 8 + (std::countr_zero(
high) >> 3);
180#if defined(__ARM_FEATURE_SVE)
181 return fastx_line_end_sve;
182#elif defined(__aarch64__) || defined(_M_ARM64)
183 return fastx_line_end_neon;
184#elif (defined(__GNUC__) || defined(__clang__)) && defined(__x86_64__)
185 if (__builtin_cpu_supports(
"avx512bw"))
return fastx_line_end_avx512;
195[[nodiscard]]
inline size_t fastx_line_end(std::string_view data,
size_t position) {
196 return line_end(data, position);
199#if defined(__linux__)
209[[nodiscard]]
inline bool
210read_whole_file(
int fd,
char* out,
size_t size,
bool direct =
false,
size_t capacity = 0) noexcept {
211 constexpr size_t page = 4096;
213 int const flags = direct && size > 64 * page ? ::fcntl(fd, F_GETFL) : -1;
214 if (flags != -1 && ::fcntl(fd, F_SETFL, flags | O_DIRECT) == 0) {
215 auto const whole = (size + page - 1) / page * page;
216 if (
reinterpret_cast<uintptr_t
>(out) % page == 0) {
218 auto const direct_end = capacity >= whole ? whole : size / page * page;
219 while (done < direct_end && done < size) {
221 ::pread(fd, out + done, direct_end - done,
static_cast<off_t
>(done));
223 done = std::min(size, done +
static_cast<size_t>(got));
226 constexpr size_t bounce_bytes =
size_t{8} << 20;
227 static thread_local std::unique_ptr<char,
decltype(&std::free)> bounce{
228 static_cast<char*
>(std::aligned_alloc(page, bounce_bytes)), &std::free
230 while (bounce && done < size && done % page == 0) {
231 auto const wanted = std::min(bounce_bytes, (size - done + page - 1) / page * page);
232 auto const got = ::pread(fd, bounce.get(), wanted,
static_cast<off_t
>(done));
234 auto const used = std::min(size - done,
static_cast<size_t>(got));
235 std::memcpy(out + done, bounce.get(), used);
238 if (done < size) ::fcntl(fd, F_SETFL, flags);
240 while (done < size) {
241 auto const got = ::pread(fd, out + done, size - done,
static_cast<off_t
>(done));
243 done +=
static_cast<size_t>(got);
260 *
this = std::move(other);
264 if (
this != &other) {
266 owned_storage_ = std::move(other.owned_storage_);
269 other.data_ =
nullptr;
271#if defined(__linux__)
272 mapped_ = other.mapped_;
273 mapped_size_ = other.mapped_size_;
274 other.mapped_ =
nullptr;
275 other.mapped_size_ = 0;
297 load(std::string
const& path,
bool read =
false,
bool direct =
false) {
298 auto buffer = std::make_unique<fastx_mapped_file>();
299 auto loaded = buffer->load_from_path(path, read, direct);
300 if (!loaded)
return Err(loaded.error());
304#if defined(__linux__)
307 adopt(
int fd,
size_t size, std::string
const& path,
bool read,
bool direct =
false) {
308 auto buffer = std::make_unique<fastx_mapped_file>();
309 auto loaded = buffer->load_from_descriptor(fd, size, path, read, direct);
310 if (!loaded)
return Err(loaded.error());
316 [[nodiscard]] std::string_view
data() const noexcept {
317 return std::string_view{data_, size_};
321 [[nodiscard]]
bool empty() const noexcept {
326 struct free_deleter {
327 void operator()(
char*
data)
const noexcept {
332 std::unique_ptr<char, free_deleter> owned_storage_;
334 [[nodiscard]]
static size_t page_rounded(
size_t size)
noexcept {
335 return (std::max<size_t>(size, 1) + 4095) / 4096 * 4096;
338 void allocate(
size_t size) {
339 owned_storage_.reset(
static_cast<char*
>(std::aligned_alloc(4096, page_rounded(size))));
340 if (!owned_storage_)
throw std::bad_alloc();
342 char const* data_{
nullptr};
345#if defined(__linux__)
346 void* mapped_{
nullptr};
347 size_t mapped_size_{0};
351#if defined(__linux__)
352 if (mapped_ !=
nullptr) {
353 munmap(mapped_, mapped_size_);
358 owned_storage_.reset();
363 [[nodiscard]] Result<void> load_from_path(
364 std::string
const& path,
365 [[maybe_unused]]
bool read,
366 [[maybe_unused]]
bool direct
368#if defined(__linux__)
369 int const fd = ::open(path.c_str(), O_RDONLY);
374 struct stat file_status{};
375 if (::fstat(fd, &file_status) != 0 || file_status.st_size < 0) {
379 return load_from_descriptor(
380 fd,
static_cast<size_t>(file_status.st_size), path, read, direct
383 std::ifstream input(path, std::ios::binary);
384 if (!input.is_open()) {
387 input.seekg(0, std::ios::end);
388 auto const file_size = input.tellg();
392 allocate(
static_cast<size_t>(file_size));
393 input.seekg(0, std::ios::beg);
395 input.read(owned_storage_.get(),
static_cast<std::streamsize
>(file_size));
400 data_ = owned_storage_.get();
401 size_ =
static_cast<size_t>(file_size);
406#if defined(__linux__)
407 [[nodiscard]] Result<void>
408 load_from_descriptor(
int fd,
size_t size, std::string
const& path,
bool read,
bool direct) {
414 mapped_ = ::mmap(
nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
416 if (mapped_ == MAP_FAILED) {
421 data_ =
static_cast<char const*
>(mapped_);
426 if (!read_whole_file(fd, owned_storage_.get(), size, direct, page_rounded(size))) {
427 owned_storage_.reset();
430 data_ = owned_storage_.get();
464 : data_(data), source_name_(std::move(source_name)) {}
472 auto header =
CUDDL_TRY(read_header_line());
473 if (header.empty()) {
477 char const tag = header.front();
481 }
else if (tag ==
'@') {
484 return Err(parse_error(
"expected FASTA or FASTQ header"));
489 if (tag != expected) {
490 return Err(parse_error(
"mixed FASTA and FASTQ records are not supported"));
493 return Err(parse_error(
"mixed FASTA and FASTQ records are not supported"));
495 auto const base = sequence.size();
496 CUDDL_TRY(read_fastq_sequence(sequence, base));
501 std::string_view data_;
502 std::string source_name_;
505 uint64_t line_number_{};
507 [[nodiscard]] Error parse_error(std::string_view message)
const {
509 std::format(
"{}:{}: {}", source_name_, line_number_, message)
513 [[nodiscard]] std::string_view read_line() {
514 if (position_ >= data_.size()) {
520 std::string_view
const line = data_.substr(position_, end - position_);
522 if (position_ < data_.size() && data_[position_] ==
'\r') {
525 if (position_ < data_.size() && data_[position_] ==
'\n') {
532 [[nodiscard]] Result<std::string_view> read_header_line() {
533 while (position_ < data_.size()) {
534 std::string_view
const line = read_line();
539 return std::string_view{};
542 [[nodiscard]] Result<void> read_fastq_sequence(std::string& sequence,
size_t base) {
543 while (position_ < data_.size()) {
544 std::string_view
const line = read_line();
545 if (!line.empty() && line.front() ==
'+') {
546 return read_fastq_qualities(sequence.size() - base);
548 sequence.append(line.data(), line.size());
550 return Err(parse_error(
"unterminated FASTQ record: missing '+' separator"));
553 [[nodiscard]] Result<void> read_fastq_qualities(uint64_t expected_length) {
554 uint64_t quality_length = 0;
555 while (quality_length < expected_length && position_ < data_.size()) {
556 std::string_view
const line = read_line();
557 quality_length += line.size();
558 if (quality_length > expected_length) {
559 return Err(parse_error(
"FASTQ quality length exceeds sequence length"));
562 if (quality_length != expected_length) {
563 return Err(parse_error(
"FASTQ quality length does not match sequence length"));
571 std::unique_ptr<fastx_mapped_file>
file;
595 for (uint32_t j = 0; j < 16; ++j) {
597 word |=
static_cast<uint32_t
>(symbol & 3U) << (2 * j);
598 bad |=
static_cast<uint32_t
>(symbol == 0xFFU) << j;
604 for (uint32_t half = 0; half < 2; ++half) {
606 std::memcpy(&x, bases + 8 * half,
sizeof(x));
607 constexpr uint64_t low7 = 0x7F7F7F7F7F7F7F7FULL;
609 auto const zero = [](uint64_t v) {
return ~(((v & low7) + low7) | v | low7); };
610 auto const upper = x & 0xDFDFDFDFDFDFDFDFULL;
611 auto const valid = zero(upper ^ 0x4141414141414141ULL) |
612 zero(upper ^ 0x4343434343434343ULL) |
613 zero(upper ^ 0x4747474747474747ULL) | zero(upper ^ 0x5454545454545454ULL);
614 auto codes = (x >> 1) & 0x0303030303030303ULL;
615 codes = (codes | (codes >> 6)) & 0x000F000F000F000FULL;
616 codes = (codes | (codes >> 12)) & 0x000000FF000000FFULL;
617 codes = (codes | (codes >> 24)) & 0xFFFFULL;
618 word |=
static_cast<uint32_t
>(codes) << (16 * half);
620 bad |=
static_cast<uint32_t
>(
621 ((~valid & 0x8080808080808080ULL) >> 7) * 0x0102040810204080ULL >> 56
631 for (
size_t piece = 0; piece <
width / 16; ++piece) {
632 auto const left =
count > 16 * piece ?
count - 16 * piece : 0;
642#if (defined(__x86_64__) || defined(__i386__)) && (defined(__GNUC__) || defined(__clang__))
644__attribute__((target(
"avx2"))) inline uint64_t
645pack_bases_avx2(
char const* bases,
size_t count, uint32_t*
words) {
647 auto const x = _mm256_loadu_si256(
reinterpret_cast<__m256i const*
>(bases));
648 auto const upper = _mm256_and_si256(x, _mm256_set1_epi8(
static_cast<char>(0xDF)));
649 auto const valid = _mm256_or_si256(
651 _mm256_cmpeq_epi8(upper, _mm256_set1_epi8(
'A')),
652 _mm256_cmpeq_epi8(upper, _mm256_set1_epi8(
'C'))
655 _mm256_cmpeq_epi8(upper, _mm256_set1_epi8(
'G')),
656 _mm256_cmpeq_epi8(upper, _mm256_set1_epi8(
'T'))
660 auto const codes = _mm256_and_si256(_mm256_srli_epi16(x, 1), _mm256_set1_epi8(3));
661 auto const pairs = _mm256_maddubs_epi16(codes, _mm256_set1_epi16(0x0401));
662 auto const quads = _mm256_madd_epi16(
pairs, _mm256_set1_epi32(0x00100001));
664 auto const gathered = _mm256_shuffle_epi8(
665 quads, _mm256_setr_epi8(0, 4, 8, 12, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
666 0, 4, 8, 12, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1)
668 auto const both = _mm256_permutevar8x32_epi32(gathered, _mm256_setr_epi32(0, 4, 0, 0, 0, 0, 0, 0));
669 _mm_storel_epi64(
reinterpret_cast<__m128i*
>(
words), _mm256_castsi256_si128(both));
670 return ~static_cast<uint64_t>(
static_cast<uint32_t
>(_mm256_movemask_epi8(valid))) &
675__attribute__((target(
"avx2,avx512f,avx512bw"))) inline uint64_t
676pack_bases_avx512(
char const* bases,
size_t count, uint32_t*
words) {
683 auto const x = _mm512_loadu_si512(bases);
684 auto const upper = _mm512_and_si512(x, _mm512_set1_epi8(
static_cast<char>(0xDF)));
685 auto const valid = _mm512_cmpeq_epi8_mask(upper, _mm512_set1_epi8(
'A')) |
686 _mm512_cmpeq_epi8_mask(upper, _mm512_set1_epi8(
'C')) |
687 _mm512_cmpeq_epi8_mask(upper, _mm512_set1_epi8(
'G')) |
688 _mm512_cmpeq_epi8_mask(upper, _mm512_set1_epi8(
'T'));
690 auto const codes = _mm512_and_si512(_mm512_srli_epi16(x, 1), _mm512_set1_epi8(3));
691 auto const pairs = _mm512_maddubs_epi16(codes, _mm512_set1_epi16(0x0401));
692 auto const quads = _mm512_madd_epi16(
pairs, _mm512_set1_epi32(0x00100001));
693 _mm_storeu_si128(
reinterpret_cast<__m128i*
>(
words), _mm512_cvtepi32_epi8(quads));
694 return ~static_cast<uint64_t>(valid);
698#if defined(__aarch64__) || defined(_M_ARM64)
702inline uint64_t pack_bases_neon(
char const* bases,
size_t count, uint32_t*
words) {
704 static constexpr int8_t shift_table[16] = {0, 2, 4, 6, 0, 2, 4, 6, 0, 2, 4, 6, 0, 2, 4, 6};
705 auto const shifts = vld1q_s8(shift_table);
706 for (
size_t piece = 0; piece < 4; ++piece) {
707 auto const x = vld1q_u8(
reinterpret_cast<uint8_t const*
>(bases) + 16 * piece);
708 auto const upper = vandq_u8(x, vdupq_n_u8(0xDF));
709 auto const valid = vorrq_u8(
710 vorrq_u8(vceqq_u8(upper, vdupq_n_u8(
'A')), vceqq_u8(upper, vdupq_n_u8(
'C'))),
711 vorrq_u8(vceqq_u8(upper, vdupq_n_u8(
'G')), vceqq_u8(upper, vdupq_n_u8(
'T')))
714 auto const invalid = vget_lane_u64(
715 vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(vmvnq_u8(valid)), 4)), 0
718 auto const codes = vshlq_u8(vandq_u8(vshrq_n_u8(x, 1), vdupq_n_u8(3)), shifts);
719 auto const pairs = vpaddq_u8(codes, codes);
720 words[piece] = vgetq_lane_u32(vreinterpretq_u32_u8(vpaddq_u8(
pairs,
pairs)), 0);
722 return ~uint64_t{0} << (16 * piece +
static_cast<size_t>(std::countr_zero(invalid)) / 4);
741#if (defined(__x86_64__) || defined(__i386__)) && (defined(__GNUC__) || defined(__clang__))
742 if (__builtin_cpu_supports(
"avx512bw"))
return {pack_bases_avx512, 64};
743 if (__builtin_cpu_supports(
"avx2"))
return {pack_bases_avx2, 32};
745#if defined(__aarch64__) || defined(_M_ARM64)
746 return {pack_bases_neon, 64};
762 if (result.
extents.empty() || k < 16)
return;
763 auto*
const out =
const_cast<char*
>(result.
extents.front().begin);
764 uint64_t written = 0;
765 for (
auto const& extent : result.
extents) {
766 auto const* next = extent.begin;
767 auto const*
const end = extent.end;
769 while (next < end &&
encode_base(*next) == 0xFF) ++next;
770 auto const*
const start = next;
771 auto const first = written;
774 auto const bad = packer.
pack(next,
static_cast<size_t>(end - next),
words);
776 bad == 0 ? packer.
width :
static_cast<size_t>(std::countr_zero(bad));
778 if (bases == packer.
width ||
static_cast<size_t>(next + bases - start) >= k) {
779 auto const count = (bases + 15) / 16;
780 std::memcpy(out + written *
sizeof(uint32_t),
words,
count *
sizeof(uint32_t));
784 if (bases != packer.
width)
break;
786 auto const bases =
static_cast<uint64_t
>(next - start);
788 result.
packed.push_back({first, bases});
794 result.
words =
reinterpret_cast<uint32_t const*
>(out);
825 libdeflate_decompressor*
handle{libdeflate_alloc_decompressor()};
830 libdeflate_free_decompressor(
handle);
837 libdeflate_decompressor* decoder,
838 std::string_view input,
844 auto const byte = [&](
size_t i) {
845 return static_cast<uint32_t
>(
static_cast<unsigned char>(input[i]));
847 if (input.size() < 18 || byte(0) != 0x1f || byte(1) != 0x8b || byte(2) != 8 ||
848 (byte(3) & 0xe0) != 0) {
849 return LIBDEFLATE_BAD_DATA;
851 auto const flags = byte(3);
852 auto const end = input.size() - 8;
854 if ((flags & 4) != 0) {
855 if (end - at < 2)
return LIBDEFLATE_BAD_DATA;
856 auto const length = byte(at) | (byte(at + 1) << 8);
858 if (length > end - at)
return LIBDEFLATE_BAD_DATA;
861 for (
auto const flag : {8U, 16U}) {
862 if ((flags & flag) != 0) {
863 auto const terminator = input.find(
'\0', at);
864 if (terminator == std::string_view::npos || terminator >= end) {
865 return LIBDEFLATE_BAD_DATA;
870 if ((flags & 2) != 0) {
871 if (end - at < 2)
return LIBDEFLATE_BAD_DATA;
874 size_t deflate_bytes = 0;
875 auto const status = libdeflate_deflate_decompress_ex(
876 decoder, input.data() + at, end - at, output, capacity, &deflate_bytes, &produced
878 if (status != LIBDEFLATE_SUCCESS)
return status;
879 auto const trailer = at + deflate_bytes;
880 auto const size = byte(trailer + 4) | (byte(trailer + 5) << 8) | (
byte(trailer + 6) << 16) |
881 (
byte(trailer + 7) << 24);
882 if (
static_cast<uint32_t
>(produced) != size)
return LIBDEFLATE_BAD_DATA;
883 consumed = trailer + 8;
884 return LIBDEFLATE_SUCCESS;
895 if (shared.
handle ==
nullptr) {
899 if (output.empty()) output.resize(std::max<size_t>(input.size() * 4, 1U << 16));
902 while (consumed < input.size()) {
903 size_t member_in = 0;
904 size_t member_out = 0;
907 input.substr(consumed),
908 output.data() + produced,
909 output.size() - produced,
913 if (status == LIBDEFLATE_INSUFFICIENT_SPACE) {
914 output.resize(output.size() * 2 + 1);
917 if (status != LIBDEFLATE_SUCCESS) {
920 consumed += member_in;
921 produced += member_out;
923 output.resize(produced);
932 std::string_view input,
935 std::string
const& path
937 insufficient =
false;
939 if (shared.
handle ==
nullptr) {
944 while (consumed < input.size()) {
945 size_t member_in = 0;
946 size_t member_out = 0;
949 input.substr(consumed),
950 target.
data + produced,
955 if (status == LIBDEFLATE_INSUFFICIENT_SPACE) {
960 if (status != LIBDEFLATE_SUCCESS) {
963 consumed += member_in;
964 produced += member_out;
978 auto file = std::fopen(path.c_str(),
"rb");
979 if (file ==
nullptr)
return 0;
981 if (std::fseek(file, -4, SEEK_END) == 0) {
982 unsigned char trailer[4] = {};
983 if (std::fread(trailer, 1,
sizeof(trailer), file) ==
sizeof(trailer)) {
984 size =
static_cast<uint64_t
>(trailer[0]) | (
static_cast<uint64_t
>(trailer[1]) << 8) |
985 (
static_cast<uint64_t
>(trailer[2]) << 16) |
986 (
static_cast<uint64_t
>(trailer[3]) << 24);
994 if (input.size() < 8)
return 0;
995 auto const byte = [&](
size_t index) {
996 return static_cast<uint32_t
>(
static_cast<unsigned char>(input[index]));
998 auto const tail = input.size() - 4;
999 return byte(tail) | (byte(tail + 1) << 8) | (
byte(tail + 2) << 16) | (
byte(tail + 3) << 24);
1002#if defined(__x86_64__) || defined(__i386__)
1003 #include <immintrin.h>
1004 #include <tmmintrin.h>
1005#elif defined(__ARM_NEON)
1006 #include <arm_neon.h>
1009namespace compact_impl {
1020 for (
int mask = 0; mask < 256; ++mask) {
1022 for (
int bit = 0; bit < 8; ++bit) {
1023 if ((mask & (1 << bit)) == 0)
control[mask][at++] =
static_cast<uint8_t
>(bit);
1025 for (; at < 16; ++at)
control[mask][at] = 0x80U;
1026 kept[mask] =
static_cast<uint8_t
>(8 - __builtin_popcount(
static_cast<unsigned>(mask)));
1036#if defined(__ARM_NEON)
1038[[nodiscard]]
inline unsigned compact_mask(uint8x16_t flags)
noexcept {
1039 constexpr std::uint8_t weights[16] = {1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128};
1040 auto const weighted = vandq_u8(flags, vld1q_u8(weights));
1041 return static_cast<unsigned>(vaddv_u8(vget_low_u8(weighted))) |
1042 (
static_cast<unsigned>(vaddv_u8(vget_high_u8(weighted))) << 8U);
1048#if defined(__ARM_NEON)
1049inline char* compact_block_neon(uint8x16_t
block, uint8x16_t whitespace,
char* out) {
1050 if (vmaxvq_u8(whitespace) == 0) {
1051 vst1q_u8(
reinterpret_cast<uint8_t*
>(out),
block);
1055 auto const flags = compact_impl::compact_mask(whitespace);
1056 auto const low = flags & 0xFFU;
1057 auto const high = (flags >> 8U) & 0xFFU;
1058 auto const packed_low = vqtbl1q_u8(
block, vld1q_u8(table.control[
low]));
1059 auto const packed_high = vqtbl1q_u8(vextq_u8(
block,
block, 8), vld1q_u8(table.control[
high]));
1061 vst1_u8(
reinterpret_cast<uint8_t*
>(out), vget_low_u8(packed_low));
1062 out += table.kept[
low];
1063 vst1_u8(
reinterpret_cast<uint8_t*
>(out), vget_low_u8(packed_high));
1064 return out + table.kept[
high];
1067inline char* compact_sequence_whitespace_neon(
char const* first,
char const* last,
char* out) {
1068 auto const newline = vdupq_n_u8(
'\n');
1069 auto const carriage = vdupq_n_u8(
'\r');
1070 auto const space = vdupq_n_u8(
' ');
1071 auto const tab = vdupq_n_u8(
'\t');
1072 auto const* at = first;
1073 while (
static_cast<size_t>(last - at) >= 16) {
1074 auto const block = vld1q_u8(
reinterpret_cast<std::uint8_t const*
>(at));
1075 auto const whitespace = vorrq_u8(
1076 vorrq_u8(vceqq_u8(
block, newline), vceqq_u8(
block, carriage)),
1077 vorrq_u8(vceqq_u8(
block, space), vceqq_u8(
block, tab))
1079 out = compact_block_neon(
block, whitespace, out);
1082 for (; at != last; ++at) {
1089#if defined(__x86_64__) || defined(__i386__)
1094__attribute__((target(
"avx2"))) inline
char*
1095compact_block_avx2(__m256i
block,
unsigned flags,
char* out) {
1097 auto const low_first = flags & 0xFFU;
1098 auto const low_second = (flags >> 8U) & 0xFFU;
1099 auto const high_first = (flags >> 16U) & 0xFFU;
1100 auto const high_second = (flags >> 24U) & 0xFFU;
1101 auto const control_first = _mm256_set_m128i(
1102 _mm_load_si128(
reinterpret_cast<__m128i const*
>(table.control[high_first])),
1103 _mm_load_si128(
reinterpret_cast<__m128i const*
>(table.control[low_first]))
1105 auto const control_second = _mm256_set_m128i(
1106 _mm_load_si128(
reinterpret_cast<__m128i const*
>(table.control[high_second])),
1107 _mm_load_si128(
reinterpret_cast<__m128i const*
>(table.control[low_second]))
1109 auto const packed_first = _mm256_shuffle_epi8(
block, control_first);
1110 auto const packed_second = _mm256_shuffle_epi8(_mm256_bsrli_epi128(
block, 8), control_second);
1114 _mm_storel_epi64(
reinterpret_cast<__m128i*
>(out), _mm256_castsi256_si128(packed_first));
1115 out += table.kept[low_first];
1116 _mm_storel_epi64(
reinterpret_cast<__m128i*
>(out), _mm256_castsi256_si128(packed_second));
1117 out += table.kept[low_second];
1118 _mm_storel_epi64(
reinterpret_cast<__m128i*
>(out), _mm256_extracti128_si256(packed_first, 1));
1119 out += table.kept[high_first];
1120 _mm_storel_epi64(
reinterpret_cast<__m128i*
>(out), _mm256_extracti128_si256(packed_second, 1));
1121 out += table.kept[high_second];
1126__attribute__((target(
"avx2"))) inline
char*
1127compact_sequence_whitespace_avx2(
char const* first,
char const* last,
char* out) {
1128 auto const newline = _mm256_set1_epi8(
'\n');
1129 auto const carriage = _mm256_set1_epi8(
'\r');
1130 auto const space = _mm256_set1_epi8(
' ');
1131 auto const tab = _mm256_set1_epi8(
'\t');
1132 auto const* at = first;
1133 while (
static_cast<size_t>(last - at) >= 32) {
1134 auto const block = _mm256_loadu_si256(
reinterpret_cast<__m256i const*
>(at));
1135 auto const whitespace = _mm256_or_si256(
1136 _mm256_or_si256(_mm256_cmpeq_epi8(
block, newline), _mm256_cmpeq_epi8(
block, carriage)),
1137 _mm256_or_si256(_mm256_cmpeq_epi8(
block, space), _mm256_cmpeq_epi8(
block, tab))
1140 compact_block_avx2(
block,
static_cast<unsigned>(_mm256_movemask_epi8(whitespace)), out);
1143 for (; at != last; ++at) {
1150static bool const simd_compaction = !
ablated(
"simd-parsing");
1152#if (defined(__x86_64__) || defined(__i386__)) && (defined(__GNUC__) || defined(__clang__))
1153static bool const compact_avx512 = simd_compaction && __builtin_cpu_supports(
"avx512bw") &&
1154 __builtin_cpu_supports(
"avx512vbmi2");
1155static bool const compact_avx2 = simd_compaction && __builtin_cpu_supports(
"avx2");
1157__attribute__((target(
"avx512f,avx512bw,avx512vbmi2,popcnt"))) inline
char*
1158compact_sequence_whitespace_avx512(
char const* first,
char const* last,
char* out) {
1159 auto const* at = first;
1160 while (
static_cast<size_t>(last - at) >= 64) {
1161 auto const block = _mm512_loadu_si512(at);
1163 ~(_mm512_cmpeq_epi8_mask(
block, _mm512_set1_epi8(
'\n')) |
1164 _mm512_cmpeq_epi8_mask(
block, _mm512_set1_epi8(
'\r')) |
1165 _mm512_cmpeq_epi8_mask(
block, _mm512_set1_epi8(
' ')) |
1166 _mm512_cmpeq_epi8_mask(
block, _mm512_set1_epi8(
'\t')));
1167 _mm512_storeu_si512(out, _mm512_maskz_compress_epi8(keep,
block));
1168 out += std::popcount(keep);
1171 for (; at != last; ++at) {
1187 if (
static_cast<size_t>(last - first) >= 64 && simd_compaction) {
1188#if defined(__ARM_NEON)
1189 return compact_sequence_whitespace_neon(first, last, out);
1190#elif defined(__x86_64__) || defined(__i386__)
1191 if (compact_avx512)
return compact_sequence_whitespace_avx512(first, last, out);
1193 return compact_sequence_whitespace_avx2(first, last, out);
1197 constexpr uint64_t ones = 0x0101010101010101ULL;
1198 auto const has_zero_byte = [](uint64_t
word) {
1199 return (
word - ones) & ~word & 0x8080808080808080ULL;
1201 auto const has_byte = [&](uint64_t
word, uint64_t byte) {
1202 return has_zero_byte(
word ^ (ones *
byte));
1204 auto const*
word = first;
1205 while (
static_cast<size_t>(last -
word) >=
sizeof(uint64_t)) {
1207 std::memcpy(&value,
word,
sizeof(value));
1208 auto const whitespace = has_byte(value,
' ') | has_byte(value,
'\t') |
1209 has_byte(value,
'\n') | has_byte(value,
'\r');
1210 if (whitespace == 0) {
1211 std::memcpy(out,
word,
sizeof(value));
1212 out +=
sizeof(value);
1214 for (
size_t index = 0; index <
sizeof(value); ++index) {
1218 word +=
sizeof(value);
1220 for (
auto const* at =
word; at != last; ++at) {
1230 for (
auto const* at = first; at != last; ++at) {
1231 at =
static_cast<char const*
>(std::memchr(at,
'>',
static_cast<size_t>(last - at)));
1232 if (at ==
nullptr)
return last;
1233 if (at == first || at[-1] ==
'\n' || at[-1] ==
'\r')
return at;
1238#if defined(__x86_64__) || defined(__i386__)
1244__attribute__((target(
"avx512f,avx512bw,avx512vbmi2,popcnt"))) inline
char*
1245fasta_sequence_compact_avx512(
char const* first,
char const* last,
char* out,
char const*& stop) {
1246 auto const newline = _mm512_set1_epi8(
'\n');
1247 auto const carriage = _mm512_set1_epi8(
'\r');
1248 auto const space = _mm512_set1_epi8(
' ');
1249 auto const tab = _mm512_set1_epi8(
'\t');
1250 auto const marker = _mm512_set1_epi8(
'>');
1253 auto const* at = first;
1254 while (
static_cast<size_t>(last - at) >= 64) {
1255 auto const block = _mm512_loadu_si512(at);
1257 _mm512_cmpeq_epi8_mask(
block, newline) | _mm512_cmpeq_epi8_mask(
block, carriage);
1258 auto const headers = _mm512_cmpeq_epi8_mask(
block, marker) & ((ends << 1U) |
carry);
1260 stop = at + std::countr_zero(headers);
1263 carry = ends >> 63U;
1265 ~(ends | _mm512_cmpeq_epi8_mask(
block, space) | _mm512_cmpeq_epi8_mask(
block, tab));
1266 _mm512_storeu_si512(out, _mm512_maskz_compress_epi8(keep,
block));
1267 out += std::popcount(keep);
1270 bool line_start =
carry != 0;
1271 for (; at != last; ++at) {
1272 char const ch = *at;
1273 if (ch ==
'>' && line_start)
break;
1274 line_start = ch ==
'\n' || ch ==
'\r';
1281__attribute__((target(
"avx2"))) inline
char*
1282fasta_sequence_compact_avx2(
char const* first,
char const* last,
char* out,
char const*& stop) {
1283 auto const newline = _mm256_set1_epi8(
'\n');
1284 auto const carriage = _mm256_set1_epi8(
'\r');
1285 auto const space = _mm256_set1_epi8(
' ');
1286 auto const tab = _mm256_set1_epi8(
'\t');
1287 auto const marker = _mm256_set1_epi8(
'>');
1290 auto const* at = first;
1291 while (
static_cast<size_t>(last - at) >= 32) {
1292 auto const block = _mm256_loadu_si256(
reinterpret_cast<__m256i const*
>(at));
1293 auto const line_end =
1294 _mm256_or_si256(_mm256_cmpeq_epi8(
block, newline), _mm256_cmpeq_epi8(
block, carriage));
1295 auto const ends =
static_cast<unsigned>(_mm256_movemask_epi8(line_end));
1296 auto const markers =
1297 static_cast<unsigned>(_mm256_movemask_epi8(_mm256_cmpeq_epi8(
block, marker)));
1298 auto const headers = markers & ((ends << 1U) |
carry);
1300 stop = at + std::countr_zero(headers);
1303 carry = ends >> 31U;
1304 auto const whitespace = _mm256_or_si256(
1306 _mm256_or_si256(_mm256_cmpeq_epi8(
block, space), _mm256_cmpeq_epi8(
block, tab))
1309 compact_block_avx2(
block,
static_cast<unsigned>(_mm256_movemask_epi8(whitespace)), out);
1312 bool line_start =
carry != 0;
1313 for (; at != last; ++at) {
1314 char const ch = *at;
1315 if (ch ==
'>' && line_start)
break;
1316 line_start = ch ==
'\n' || ch ==
'\r';
1324#if defined(__ARM_NEON)
1326fasta_sequence_compact_neon(
char const* first,
char const* last,
char* out,
char const*& stop) {
1328 auto const* at = first;
1329 while (
static_cast<size_t>(last - at) >= 16) {
1330 auto const block = vld1q_u8(
reinterpret_cast<uint8_t const*
>(at));
1331 auto const line_end =
1332 vorrq_u8(vceqq_u8(
block, vdupq_n_u8(
'\n')), vceqq_u8(
block, vdupq_n_u8(
'\r')));
1333 auto const ends = compact_impl::compact_mask(line_end);
1334 auto const markers = compact_impl::compact_mask(vceqq_u8(
block, vdupq_n_u8(
'>')));
1335 auto const headers = markers & ((ends << 1U) |
carry);
1337 stop = at + std::countr_zero(headers);
1340 carry = ends >> 15U;
1341 auto const whitespace = vorrq_u8(
1342 line_end, vorrq_u8(vceqq_u8(
block, vdupq_n_u8(
' ')), vceqq_u8(
block, vdupq_n_u8(
'\t')))
1344 out = compact_block_neon(
block, whitespace, out);
1347 bool line_start =
carry != 0;
1348 for (; at != last; ++at) {
1349 char const ch = *at;
1350 if (ch ==
'>' && line_start)
break;
1351 line_start = ch ==
'\n' || ch ==
'\r';
1367#if (defined(__x86_64__) || defined(__i386__)) && (defined(__GNUC__) || defined(__clang__))
1368 if (compact_avx512)
return fasta_sequence_compact_avx512(first, last, out, stop);
1369 if (compact_avx2)
return fasta_sequence_compact_avx2(first, last, out, stop);
1371#if defined(__ARM_NEON)
1372 if (simd_compaction)
return fasta_sequence_compact_neon(first, last, out, stop);
1383[[nodiscard]]
inline std::vector<fastx_sequence_extent>
1385 std::vector<fastx_sequence_extent> extents;
1386 size_t header = data.find(
'>');
1387 while (header != std::string_view::npos && header != 0 && data[header - 1] !=
'\n' &&
1388 data[header - 1] !=
'\r') {
1389 header = data.find(
'>', header + 1);
1391 if (header == std::string_view::npos)
return extents;
1392 auto const*
const last = data.data() + data.size();
1395 if (end + 1 >= data.size())
break;
1396 auto const*
const sequence = data.data() + end + 1;
1397 char const* stop =
nullptr;
1398 auto*
const begin = out;
1400 if (stop != sequence) extents.push_back({begin, out});
1401 if (stop == last)
break;
1402 header =
static_cast<size_t>(stop - data.data());
1414 if (result.
extents.empty())
return;
1418 struct writable_buffer {
1422 auto const buffers = std::array{
1427 auto const writable = [&](
char const* first,
size_t size) {
1428 return std::any_of(buffers.begin(), buffers.end(), [&](writable_buffer
const& buffer) {
1429 return buffer.data != nullptr && buffer.size >= size && first >= buffer.data &&
1430 first + size <= buffer.data + buffer.size;
1433 size_t raw_total = 0;
1434 for (
auto const& extent : result.
extents)
1435 raw_total +=
static_cast<size_t>(extent.end - extent.begin);
1438 copied.reserve(raw_total);
1439 std::vector<std::pair<size_t, std::pair<size_t, size_t>>>
offsets;
1440 for (
size_t index = 0; index < result.
extents.size(); ++index) {
1441 auto const& extent = result.
extents[index];
1442 auto const size =
static_cast<size_t>(extent.end - extent.begin);
1443 if (writable(extent.begin, size)) {
1446 extent.begin, extent.end,
const_cast<char*
>(extent.begin)
1448 result.
extents[index] = {extent.begin, write};
1450 auto const begin = copied.size();
1452 copied.resize(begin + size);
1455 copied.resize(
static_cast<size_t>(write - copied.data()));
1456 offsets.emplace_back(index, std::pair{begin, copied.size()});
1459 if (!copied.empty()) {
1461 for (
auto const& [index, span] :
offsets) {
1471 auto const data = std::exchange(result.
input, {});
1473 auto& extents = result.
extents;
1474 auto const first = data.find_first_not_of(
"\r\n");
1475 if (first != std::string_view::npos && data[first] ==
'@') {
1477 std::vector<std::pair<size_t, size_t>> records;
1479 auto const begin = sequence.size();
1483 if (sequence.size() > begin) records.emplace_back(begin, sequence.size());
1485 if (sequence.empty())
return Ok();
1486 for (
auto const& [begin, end] : records) {
1487 extents.push_back({sequence.data() + begin, sequence.data() + end});
1494 if (out ==
nullptr) {
1499 if (extents.empty() && !data.empty()) {
1510 std::string_view data,
1513 std::string
const& path
1515 size_t header = data.find(
'>');
1516 while (header != std::string_view::npos && header != 0 && data[header - 1] !=
'\n' &&
1517 data[header - 1] !=
'\r') {
1518 header = data.find(
'>', header + 1);
1520 if (header == std::string_view::npos) {
1524 std::string scratch,
carry;
1526 uint64_t written = 0;
1527 bool has_sequence =
false;
1528 auto const*
const last = data.data() + data.size();
1531 if (end + 1 >= data.size())
break;
1532 auto const* at = data.data() + end + 1;
1534 while (at != last) {
1535 auto const offset =
static_cast<size_t>(at - data.data());
1536 auto const boundary = offset + std::min<size_t>(16384, data.size() - offset);
1537 auto const limit = boundary == data.size()
1540 auto const*
const chunk_end = data.data() + limit;
1541 scratch.resize(
carry.size() +
static_cast<size_t>(chunk_end - at));
1542 std::memcpy(scratch.data(),
carry.data(),
carry.size());
1543 char const* stop =
nullptr;
1546 has_sequence |= stop != at;
1547 auto const bases =
static_cast<size_t>(compact_end - scratch.data());
1548 carry.assign(compact_end - std::min<size_t>(k - 1, bases), compact_end);
1549 chunk.
extents.push_back({scratch.data(), compact_end});
1552 for (
auto record : chunk.
packed) {
1553 words = record.word + (record.bases + 15) / 16;
1554 record.word += written;
1555 result.
packed.push_back(record);
1557 std::memcpy(out + written *
sizeof(uint32_t), scratch.data(),
words *
sizeof(uint32_t));
1561 if (stop != chunk_end || stop == last)
break;
1563 if (at == last)
break;
1564 header =
static_cast<size_t>(at - data.data());
1566 if (!has_sequence && !data.empty()) {
1569 result.
words =
reinterpret_cast<uint32_t const*
>(out);
1576 std::unique_ptr<fastx_sequence_file> result,
1577 std::string_view data,
1578 std::string
const& path,
1582 result->input = data;
1584 auto const first = data.find_first_not_of(
"\r\n");
1585 if (first != std::string_view::npos && data[first] ==
'>') {
1588 if (end + 1 < data.size() && data[end + 1] !=
'>')
return result;
1592 auto const first = data.find_first_not_of(
"\r\n");
1593 if (first == std::string_view::npos || data[first] !=
'@') {
1594 char* out = result->decompressed_target;
1595 if (out ==
nullptr) {
1596 if (data.data() != result->decompressed.data())
1597 result->decompressed.resize(data.size());
1598 out = result->decompressed.data();
1617 std::string_view input,
1618 std::string
const& path,
1622 thread_local std::unique_ptr<char[]> scratch;
1623 thread_local size_t capacity = 0;
1628 size_t wanted = hint >= input.size() ? hint : input.size() * 8;
1629 size_t produced = 0;
1631 if (capacity < wanted) {
1632 capacity = std::bit_ceil(wanted);
1633 scratch = std::make_unique_for_overwrite<char[]>(capacity);
1635 bool insufficient =
false;
1639 if (!insufficient)
break;
1640 wanted = capacity * 2;
1642 std::string_view
const data{scratch.get(), produced};
1643 auto const first = data.find_first_not_of(
"\r\n");
1644 bool const fastq = first != std::string_view::npos && data[first] ==
'@';
1646 uint64_t bytes = produced;
1649 for (
auto const& record : result.
packed) {
1650 words = std::max(
words, record.word + (record.bases + 15) / 16);
1652 bytes =
words *
sizeof(uint32_t);
1654 auto target = source.
request(bytes);
1655 char* out = target.
data;
1656 if (out ==
nullptr) {
1664 std::memcpy(out, scratch.get(), bytes);
1665 if (
fastq)
return std::optional{std::string_view{out, bytes}};
1666 result.
words =
reinterpret_cast<uint32_t const*
>(out);
1667 return std::optional<std::string_view>{};
1671 std::unique_ptr<fastx_mapped_file> file,
1672 std::string
const& path,
1677 auto result = std::make_unique<fastx_sequence_file>();
1678 result->file = std::move(file);
1679 auto data = result->file->data();
1680 auto& decompressed = result->decompressed;
1681 if (data.size() >= 2 &&
static_cast<unsigned char>(data[0]) == 0x1f &&
1682 static_cast<unsigned char>(data[1]) == 0x8b) {
1683 if (pack_k >= 16 && !defer_fasta && source.
acquire !=
nullptr) {
1685 if (!
fastq)
return result;
1696 auto request = hint >= data.size() ? hint : data.size() * 8;
1697 auto target = source.
request(request);
1698 bool insufficient =
false;
1699 bool inflated =
false;
1700 size_t produced = 0;
1701 while (target.data !=
nullptr && target.capacity > 0) {
1703 if (!written)
return Err(written.error());
1704 produced = *written;
1705 if (!insufficient) {
1709 request = std::max(request * 2, data.size());
1710 target = source.
request(request);
1713 result->decompressed_target = target.
data;
1714 result->decompressed_size = produced;
1715 result->storage_owner = std::move(target.owner);
1716 data = std::string_view{target.data, produced};
1718 if (result->decompressed_target ==
nullptr) {
1720 if (!inflated)
return Err(inflated.error());
1721 data = decompressed;
1723 }
else if (source.
acquire !=
nullptr && !defer_fasta) {
1726 auto target = source.
request(data.size());
1727 if (target.data !=
nullptr) {
1728 std::memcpy(target.data, data.data(), data.size());
1729 result->decompressed_target = target.data;
1730 result->decompressed_size = data.size();
1731 result->storage_owner = std::move(target.owner);
1732 data = std::string_view{target.data, data.size()};
1739 std::string
const& path,
1741 bool defer_fasta =
false,
1745#if defined(__linux__)
1746 if (source.acquire !=
nullptr) {
1747 int const fd = ::open(path.c_str(), O_RDONLY);
1748 struct stat status{};
1749 if (fd == -1 || ::fstat(fd, &status) != 0 || status.st_size < 0) {
1750 if (fd != -1) ::close(fd);
1753 auto const size =
static_cast<size_t>(status.st_size);
1754 unsigned char magic[2] = {};
1755 bool const gzip = ::pread(fd, magic,
sizeof(magic), 0) ==
sizeof(magic) &&
1756 magic[0] == 0x1f && magic[1] == 0x8b;
1759 auto target = gzip ? decompression_target{} : source.request(size);
1760 if (target.data ==
nullptr) {
1761 auto file = fastx_mapped_file::adopt(fd, size, path, read, source.direct_reads);
1765 if (!read_whole_file(fd, target.data, size, source.direct_reads, target.capacity)) {
1768 auto result = std::make_unique<fastx_sequence_file>();
1769 result->decompressed_target = target.data;
1770 result->decompressed_size = size;
1771 result->storage_owner = std::move(target.owner);
1773 std::move(result), {target.data, size}, path, defer_fasta, pack_k
1809 std::span<std::filesystem::path const> paths,
1813 bool defer_fasta =
false,
1814 bool completion_order =
false,
1818 workers_(std::max(size_t{1}, workers)),
1819 window_(std::max({
size_t{1}, window, workers_})),
1821 defer_fasta_(defer_fasta),
1823 results_(paths.size()),
1824 errors_(paths.size()),
1825 ready_(completion_order ? window_ : 0),
1826 retired_(defer_fasta ? window_ : 0) {
1827 threads_.reserve(workers_);
1828 if (defer_fasta_) retirer_ = std::thread([
this] { retire_files(); });
1831 auto const readers =
1832 ablated(
"read-depth") ? workers_
1834 readers_.reserve(readers);
1835 for (
size_t i = 0; i < readers; ++i) {
1836 readers_.emplace_back([
this] { read_files(); });
1838 for (
size_t i = 0; i < workers_; ++i) threads_.emplace_back([
this] { work(); });
1842 std::lock_guard lock(mutex_);
1845 assign_.notify_all();
1846 mapped_ready_.notify_all();
1847 for (
auto& thread : readers_) thread.join();
1848 for (
auto& thread : threads_) thread.join();
1850 if (retirer_.joinable()) {
1852 std::lock_guard lock(retired_mutex_);
1853 retire_stop_ =
true;
1855 retired_ready_.notify_one();
1863 std::unique_lock lock(mutex_);
1864 filled_.wait(lock, [&] {
return results_[id].has_value() || errors_[
id] !=
nullptr; });
1868 assign_.notify_one();
1870 if (errors_[
id] !=
nullptr) std::rethrow_exception(errors_[
id]);
1871 return std::move(*results_[
id]);
1877 std::unique_lock lock(mutex_);
1878 filled_.wait(lock, [&] {
return ready_count_ != 0; });
1879 auto const id = ready_[ready_head_];
1880 ready_head_ = (ready_head_ + 1) % ready_.size();
1883 assign_.notify_one();
1885 if (errors_[
id] !=
nullptr) std::rethrow_exception(errors_[
id]);
1886 return std::pair{id,
CUDDL_TRY(std::move(*results_[
id]))};
1893 std::unique_lock lock(mutex_);
1894 filled_.wait(lock, [&] {
return ready_count_ != 0 || taken_ >= limit_; });
1895 if (ready_count_ == 0)
return std::nullopt;
1902 std::lock_guard lock(mutex_);
1903 return ready_count_;
1912 std::lock_guard lock(mutex_);
1913 limit_ = std::clamp(limit, claimed_end_, limit_);
1916 assign_.notify_all();
1917 filled_.notify_all();
1918 mapped_ready_.notify_all();
1924 struct mapped_input {
1926 std::optional<Result<std::unique_ptr<fastx_mapped_file>>> file;
1927 std::exception_ptr error;
1934 std::unique_lock lock(mutex_);
1935 assign_.wait(lock, [&] {
1936 return stop_ || mapped_next_ >= limit_ || mapped_next_ - taken_ < window_;
1938 if (stop_ || mapped_next_ >= limit_)
return;
1939 id = mapped_next_++;
1947 input.error = std::current_exception();
1950 std::lock_guard lock(mutex_);
1951 mapped_.push_back(std::move(input));
1953 mapped_ready_.notify_one();
1957 void retire(std::unique_ptr<fastx_mapped_file> file) {
1958 std::unique_lock lock(retired_mutex_);
1959 retired_space_.wait(lock, [&] {
return retired_count_ < retired_.size(); });
1960 retired_[(retired_head_ + retired_count_) % retired_.size()] = std::move(file);
1963 retired_ready_.notify_one();
1966 void retire_files() {
1968 std::unique_ptr<fastx_mapped_file> file;
1970 std::unique_lock lock(retired_mutex_);
1971 retired_ready_.wait(lock, [&] {
return retire_stop_ || retired_count_ != 0; });
1972 if (retired_count_ == 0)
return;
1973 file = std::move(retired_[retired_head_]);
1974 retired_head_ = (retired_head_ + 1) % retired_.size();
1977 retired_space_.notify_one();
1986 std::unique_lock lock(mutex_);
1989 mapped_ready_.wait(lock, [&] {
1990 return stop_ || claimed_ >= limit_ || !mapped_.empty();
1992 if (stop_ || claimed_ >= limit_)
return;
1993 input = std::move(mapped_.front());
1994 mapped_.pop_front();
1996 if (
id >= limit_)
continue;
1998 claimed_end_ = std::max(claimed_end_,
id + 1);
2000 if (!mapped_.empty()) mapped_ready_.notify_one();
2003 auto const path = paths_[id].string();
2004 auto loaded = [&]() -> Result<std::unique_ptr<fastx_sequence_file>> {
2005 if (input.error) std::rethrow_exception(input.error);
2009 std::move(**input.file), path, source_, defer_fasta_, pack_k_
2014 if (defer_fasta_ && loaded && (*loaded)->file &&
2015 ((*loaded)->decompressed_target !=
nullptr ||
2016 !(*loaded)->decompressed.empty())) {
2017 retire(std::move((*loaded)->file));
2019 std::lock_guard lock(mutex_);
2020 results_[id] = std::move(loaded);
2021 if (!ready_.empty()) {
2022 ready_[(ready_head_ + ready_count_) % ready_.size()] = id;
2026 std::lock_guard lock(mutex_);
2027 errors_[id] = std::current_exception();
2028 if (!ready_.empty()) {
2029 ready_[(ready_head_ + ready_count_) % ready_.size()] = id;
2033 filled_.notify_all();
2036 std::span<std::filesystem::path const> paths_;
2039 decompression_source source_;
2042 std::vector<std::optional<Result<std::unique_ptr<fastx_sequence_file>>>> results_;
2043 std::vector<std::exception_ptr> errors_;
2044 std::vector<size_t> ready_;
2045 std::vector<std::unique_ptr<fastx_mapped_file>> retired_;
2046 std::deque<mapped_input> mapped_;
2047 std::vector<std::thread> threads_;
2048 std::thread retirer_;
2049 std::vector<std::thread> readers_;
2051 std::mutex retired_mutex_;
2052 std::condition_variable assign_, filled_;
2053 std::condition_variable retired_ready_, retired_space_;
2054 std::condition_variable mapped_ready_;
2056 size_t claimed_ = 0, claimed_end_ = 0, taken_ = 0, limit_ = paths_.size();
2057 size_t ready_head_ = 0, ready_count_ = 0;
2058 size_t retired_head_ = 0, retired_count_ = 0;
2059 size_t mapped_next_ = 0;
2061 bool retire_stop_ =
false;
FASTQ parser over a contiguous in-memory buffer.
fastx_fastq_reader(std::string_view data, std::string source_name="<buffer>")
Constructs a reader over a contiguous in-memory FASTQ buffer.
Result< bool > append_next_record(std::string &sequence)
Appends the next record's sequence bytes to sequence.
size_t ready()
Completion-order results waiting to be taken.
fastx_load_pool & operator=(fastx_load_pool const &)=delete
fastx_load_pool(fastx_load_pool const &)=delete
fastx_load_pool(std::span< std::filesystem::path const > paths, size_t workers, decompression_source source={}, size_t window=0, bool defer_fasta=false, bool completion_order=false, uint32_t pack_k=0)
Result< std::pair< size_t, std::unique_ptr< fastx_sequence_file > > > take_ready()
size_t cap(size_t limit)
Stops the loaders before id limit, or after the ids they already claimed.
Result< std::optional< std::pair< size_t, std::unique_ptr< fastx_sequence_file > > > > take_ready_until_limit()
take_ready, or nothing once every id below the cap limit is taken.
Result< std::unique_ptr< fastx_sequence_file > > take(size_t id)
Read-only contiguous file payload for in-memory FASTX parsing.
std::string_view data() const noexcept
Contiguous read-only file bytes.
fastx_mapped_file & operator=(fastx_mapped_file const &)=delete
fastx_mapped_file & operator=(fastx_mapped_file &&other) noexcept
static Result< std::unique_ptr< fastx_mapped_file > > load(std::string const &path, bool read=false, bool direct=false)
Maps or reads a file.
bool empty() const noexcept
True when the file is empty or load produced no bytes.
fastx_mapped_file(fastx_mapped_file &&other) noexcept
fastx_mapped_file(fastx_mapped_file const &)=delete
fastx_mapped_file()=default
#define CUDDL_TRY(expr)
Propagates a cuddl::Result failure from the enclosing function (GNU statement expression).
compact_shuffle_table const & compact_shuffle() noexcept
Result< void > pack_fasta_chunks(fastx_sequence_file &result, std::string_view data, char *out, uint32_t k, std::string const &path)
Compact and pack one cache-sized block at a time, without a genome-sized ASCII write.
std::vector< fastx_sequence_extent > fastx_fasta_compact_extents(std::string_view data, char *out)
Collects FASTA sequence extents with whitespace compacted out, in one pass over data.
size_t fastx_line_end(std::string_view data, size_t position)
First line-end position at or after position using the resolved scanner.
Result< std::unique_ptr< fastx_sequence_file > > finish_fastx_sequence_file(std::unique_ptr< fastx_sequence_file > result, std::string_view data, std::string const &path, bool defer_fasta, uint32_t pack_k)
Parses, packs or defers the bytes data of a loaded file, which result holds.
uint32_t pack_bases16_portable(char const *bases, size_t count, uint32_t &word)
Packs up to sixteen bases into a two-bit word, base j in bits 2j and 2j+1.
void compact_fastx_sequence_extents(fastx_sequence_file &result)
Compacts sequence whitespace in place, leaving every extent a run of bases.
Result< void > gunzip_members_into(std::string_view input, std::string &output, std::string const &path)
Decompresses gzip members from a mapped buffer into output, appending.
size_t fastx_line_end_scalar(std::string_view data, size_t position)
constexpr size_t parallel_read_depth
Reads a parallel file system needs in flight to give one node its bandwidth: on MOGON's Lustre,...
Result< std::unique_ptr< fastx_sequence_file > > decode_fastx_sequence_file(std::unique_ptr< fastx_mapped_file > file, std::string const &path, decompression_source source, bool defer_fasta, uint32_t pack_k=0)
void pack_fastx_sequence_file(fastx_sequence_file &result, uint32_t k)
Packs a parsed file's records in place into two-bit words, run by run.
char * compact_sequence_whitespace(char const *first, char const *last, char *out)
Copies bases from [first, last) to out, dropping sequence whitespace.
uint64_t pack_bases_pieces(char const *bases, size_t count, uint32_t *words, size_t width)
Packs width bases from sixteen-base pieces, for a tail shorter than a full step.
decltype(&fastx_line_end_scalar) fastx_line_end_fn
fastx_format
Detected record format for a FASTX buffer.
@ fasta
FASTA (> headers).
@ fastq
FASTQ (@ headers).
@ unknown
Format not yet determined from the first header.
Result< void > parse_fastx_sequence_file(fastx_sequence_file &result, std::string const &path)
constexpr bool fastx_is_sequence_whitespace(char ch) noexcept
True for bytes that FASTA sequence consumers conventionally skip between bases.
Result< std::unique_ptr< fastx_sequence_file > > load_fastx_sequence_file(std::string const &path, decompression_source source={}, bool defer_fasta=false, bool read=false, uint32_t pack_k=0)
char const * fasta_next_header(char const *first, char const *last)
First '>' in [first, last) that starts a line, or last.
Result< std::optional< std::string_view > > inflate_and_pack(fastx_sequence_file &result, std::string_view input, std::string const &path, decompression_source source, uint32_t k)
Inflates gzip input in this thread's reusable heap buffer and, for FASTA, packs it there and moves on...
size_t gzip_size_hint(std::string_view input)
__global__ uint16_t const uint32_t uint32_t uint32_t unsigned long long * work
constexpr uint8_t encode_base(char base) noexcept
Maps one DNA byte to its 2-bit symbol (A=0, C=1, T=2, G=3) or 0xff when invalid.
constexpr bool ablated(std::string_view) noexcept
Always false: this build compiles the ablation switches out.
uint64_t gzip_decompressed_size(std::string const &path)
Uncompressed size the gzip trailer advertises, or 0 when it cannot be read.
static __global__ uint32_t const * offsets
libdeflate_result decompress_gzip_member(libdeflate_decompressor *decoder, std::string_view input, char *output, size_t capacity, size_t &consumed, size_t &produced)
Decodes one gzip member, checking structure and ISIZE but not checksums.
Result< size_t > gunzip_members_direct(std::string_view input, decompression_target target, bool &insufficient, std::string const &path)
Decompresses gzip members straight into a caller's buffer.
uint64_t pack_bases_portable(char const *bases, size_t count, uint32_t *words)
char * fasta_sequence_compact(char const *first, char const *last, char *out, char const *&stop)
Compacts one FASTA record's sequence from first to out, stopping at the next header.
bases_packer resolve_bases_packer()
Selects the widest base packer the host supports.
fastx_line_end_fn resolve_fastx_line_end()
Selects the fastest line-end scanner the host supports.
Batch operations over contiguous device rows.
Result< void > Ok() noexcept
Success return for Result<void>; same as return {}.
cuda::std::unexpected< Error > Err(Error error)
Failure return; converts to any Result<T> via cuda::std::unexpected.
static Error invalid_argument(std::string message)
static Error resource(std::string message)
Fallible API result: cuda::std::expected<T, Error> with cuDDL factories.
static Result ok(T value)
A base packer and the bases it takes per step, a multiple of sixteen up to 64.
uint64_t(* pack)(char const *bases, size_t count, uint32_t *words)
Shuffle controls per whitespace mask, for the vector paths.
compact_shuffle_table() noexcept
Supplies storage for one file, or a null target when none is available.
bool direct_reads
Read files with O_DIRECT, as read_whole_file does.
decompression_target request(size_t bytes) const
decompression_target(* acquire)(void *context, size_t bytes)
Host-visible buffer a caller offers for decompressed bytes.
std::shared_ptr< void > owner
One run of bases packed two bits each: its first word and its length in bases.
A contiguous run of sequence bases (record headers and line breaks excluded).
std::unique_ptr< fastx_mapped_file > file
std::vector< fastx_sequence_extent > extents
char * decompressed_target
std::vector< fastx_packed_record > packed
std::shared_ptr< void > storage_owner
libdeflate handle, one per thread because the object is single-threaded.
~shared_gzip_decompressor()
libdeflate_decompressor * handle
shared_gzip_decompressor()=default
shared_gzip_decompressor & operator=(shared_gzip_decompressor const &)=delete
shared_gzip_decompressor(shared_gzip_decompressor const &)=delete
How a build moved sequence bytes to the device.
bool measure_resident
Measure GPU row reset and sketch construction, excluding input transfers and readback.
double resident_compute_ms