#include "simdutf_vendor.hh" #include #include #include #include #include extern "C" { #include #include #include #include #include #include } namespace { // Keep these values in sync with Encoding.t in simdutf.ml. enum encoding { utf8, utf16le, utf16be, utf32le, utf32be, latin1 }; // Status codes beyond upstream's error_code. Codes above incomplete_code_unit // become OCaml exceptions. Keep in sync with simdutf.ml. constexpr int incomplete_code_unit = 12; constexpr int overlapping_slices = 13; constexpr int out_of_memory = 14; constexpr int size_overflow = 15; constexpr size_t max_string_length = size_t(Max_wosize) * sizeof(value) - 1; struct outcome { int code; size_t count; // Input byte offset on error; output size/count on success. }; struct output_size_overflow {}; size_t checked_output_bytes(size_t units, size_t width = 1) { if (units > size_t(Max_long) / width) throw output_size_overflow{}; return units * width; } size_t unit_size(int enc) { if (enc == utf16le || enc == utf16be) return 2; if (enc == utf32le || enc == utf32be) return 4; return 1; } char32_t swap32(char32_t x) { return ((x & 0xff) << 24) | ((x & 0xff00) << 8) | ((x >> 8) & 0xff00) | ((x >> 24) & 0xff); } bool swap_utf32(int enc) { return (enc == utf32be) != bool(SIMDUTF_IS_BIG_ENDIAN); } // Wide input is copied into properly aligned, typed storage. In particular, // bigstring slices may start at an arbitrary byte address. UTF-32 APIs operate // on native-endian values; UTF-16 APIs accept explicit byte order. struct input_view { int enc; const char *bytes; size_t size; std::vector u16; std::vector u32; input_view(int encoding, const char *data, size_t length) : enc(encoding), bytes(data), size(length) { if (unit_size(enc) == 2) { u16.resize(length / 2); if (length) std::memcpy(u16.data(), data, length); } else if (unit_size(enc) == 4) { u32.resize(length / 4); if (length) std::memcpy(u32.data(), data, length); if (swap_utf32(enc)) { for (auto &c : u32) c = swap32(c); } } } simdutf::result validate() const { if (size == 0) return {simdutf::SUCCESS, 0}; switch (enc) { case utf8: return simdutf::validate_utf8_with_errors(bytes, size); case utf16le: return simdutf::validate_utf16le_with_errors(u16.data(), u16.size()); case utf16be: return simdutf::validate_utf16be_with_errors(u16.data(), u16.size()); case utf32le: case utf32be: return simdutf::validate_utf32_with_errors(u32.data(), u32.size()); case latin1: return {simdutf::SUCCESS, size}; default: return {simdutf::OTHER, 0}; } } }; // No OCaml allocation or exception raising inside these native scopes. C++ // buffers are destroyed before an OCaml allocation can trigger GC/longjmp. template auto native_call(F f) -> decltype(f()) { bool overflow = false; try { return f(); } catch (const std::bad_alloc &) { } catch (const std::length_error &) { } catch (const output_size_overflow &) { overflow = true; } if (overflow) caml_invalid_argument("Simdutf: output exceeds maximum buffer length"); caml_raise_out_of_memory(); } outcome validate_or_count(int enc, const char *data, size_t size, bool count) { const size_t unit = unit_size(enc); if (size % unit) return {incomplete_code_unit, size - size % unit}; if (size == 0) return {0, 0}; input_view input(enc, data, size); const auto r = input.validate(); if (r.error != simdutf::SUCCESS) return {int(r.error), r.count * unit}; if (!count) return {0, size}; switch (enc) { case utf8: return {0, simdutf::count_utf8(data, size)}; case utf16le: return {0, simdutf::count_utf16le(input.u16.data(), input.u16.size())}; case utf16be: return {0, simdutf::count_utf16be(input.u16.data(), input.u16.size())}; default: return {0, size / unit}; } } bool valid(int enc, const char *data, size_t size) { // The common paths need no temporary allocation or diagnostic tuple. if (enc == utf8) return simdutf::validate_utf8(data, size); if (enc == latin1) return true; return validate_or_count(enc, data, size, false).code == 0; } // Preflight both validity and output size before any write. In particular, // Latin-1 representability must be checked before calling an unbounded SIMD // conversion with an exact-sized destination. outcome measure_conversion(const input_view &input, int dst) { const int src = input.enc; const size_t size = input.size, units = size / unit_size(src); const auto r = input.validate(); if (r.error != simdutf::SUCCESS) return {int(r.error), r.count * unit_size(src)}; if (size == 0 || src == dst) return {0, size}; size_t count = 0; if (dst == latin1) { if (src == utf8) { for (size_t i = 0; i < size; ++count) { const auto c = static_cast(input.bytes[i]); if (c >= 0xc4) return {simdutf::TOO_LARGE, i}; i += c < 0x80 ? 1 : 2; } } else if (unit_size(src) == 2) { const bool swap = (src == utf16be) != bool(SIMDUTF_IS_BIG_ENDIAN); for (size_t i = 0; i < units; ++i) { const char16_t c = input.u16[i]; const char16_t native = swap ? char16_t((c >> 8) | (c << 8)) : c; if (native > 0xff) return {simdutf::TOO_LARGE, i * 2}; } count = units; } else { for (size_t i = 0; i < units; ++i) { if (input.u32[i] > 0xff) return {simdutf::TOO_LARGE, i * 4}; } count = units; } return {0, count}; } switch (src) { case utf8: count = unit_size(dst) == 2 ? simdutf::utf16_length_from_utf8(input.bytes, size) : simdutf::utf32_length_from_utf8(input.bytes, size); break; case latin1: count = dst == utf8 ? simdutf::utf8_length_from_latin1(input.bytes, size) : size; break; case utf16le: case utf16be: if (dst == utf8) count = src == utf16le ? simdutf::utf8_length_from_utf16le(input.u16.data(), units) : simdutf::utf8_length_from_utf16be(input.u16.data(), units); else if (unit_size(dst) == 4) count = src == utf16le ? simdutf::utf32_length_from_utf16le(input.u16.data(), units) : simdutf::utf32_length_from_utf16be(input.u16.data(), units); else count = units; break; case utf32le: case utf32be: if (dst == utf8) count = simdutf::utf8_length_from_utf32(input.u32.data(), units); else if (unit_size(dst) == 2) count = simdutf::utf16_length_from_utf32(input.u32.data(), units); else count = units; break; } return {0, checked_output_bytes(count, unit_size(dst))}; } outcome transcoded_length(int src, int dst, const char *data, size_t size) { const size_t unit = unit_size(src); if (size % unit) return {incomplete_code_unit, size - size % unit}; return measure_conversion(input_view(src, data, size), dst); } outcome convert(int src, int dst, const char *data, size_t size, char *output, size_t capacity) { const size_t src_unit = unit_size(src), dst_unit = unit_size(dst); if (size % src_unit) return {incomplete_code_unit, size - size % src_unit}; if (size == 0) return {0, 0}; input_view input(src, data, size); const auto measured = measure_conversion(input, dst); if (measured.code != 0) return measured; if (measured.count > capacity) return {simdutf::OUTPUT_BUFFER_TOO_SMALL, 0}; if (src == dst) { std::memcpy(output, data, size); return {0, size}; } const size_t units = size / src_unit; std::vector out16; std::vector out32; if (dst_unit == 2) out16.resize(measured.count / 2); if (dst_unit == 4) out32.resize(measured.count / 4); simdutf::result r; switch (src) { case utf8: switch (dst) { case utf16le: r = simdutf::convert_utf8_to_utf16le_with_errors(data, size, out16.data()); break; case utf16be: r = simdutf::convert_utf8_to_utf16be_with_errors(data, size, out16.data()); break; case utf32le: case utf32be: r = simdutf::convert_utf8_to_utf32_with_errors(data, size, out32.data()); break; default: r = simdutf::convert_utf8_to_latin1_with_errors(data, size, output); break; } break; case latin1: switch (dst) { case utf8: r = {simdutf::SUCCESS, simdutf::convert_latin1_to_utf8(data, size, output)}; break; case utf16le: r = {simdutf::SUCCESS, simdutf::convert_latin1_to_utf16le(data, size, out16.data())}; break; case utf16be: r = {simdutf::SUCCESS, simdutf::convert_latin1_to_utf16be(data, size, out16.data())}; break; default: r = {simdutf::SUCCESS, simdutf::convert_latin1_to_utf32(data, size, out32.data())}; break; } break; case utf16le: case utf16be: switch (dst) { case utf8: r = src == utf16le ? simdutf::convert_utf16le_to_utf8_with_errors(input.u16.data(), units, output) : simdutf::convert_utf16be_to_utf8_with_errors(input.u16.data(), units, output); break; case latin1: r = src == utf16le ? simdutf::convert_utf16le_to_latin1_with_errors(input.u16.data(), units, output) : simdutf::convert_utf16be_to_latin1_with_errors(input.u16.data(), units, output); break; case utf32le: case utf32be: r = src == utf16le ? simdutf::convert_utf16le_to_utf32_with_errors( input.u16.data(), units, out32.data()) : simdutf::convert_utf16be_to_utf32_with_errors( input.u16.data(), units, out32.data()); break; default: simdutf::change_endianness_utf16(input.u16.data(), units, out16.data()); r = {simdutf::SUCCESS, units}; break; } break; case utf32le: case utf32be: switch (dst) { case utf8: r = simdutf::convert_utf32_to_utf8_with_errors(input.u32.data(), units, output); break; case latin1: r = simdutf::convert_utf32_to_latin1_with_errors(input.u32.data(), units, output); break; case utf16le: r = simdutf::convert_utf32_to_utf16le_with_errors(input.u32.data(), units, out16.data()); break; case utf16be: r = simdutf::convert_utf32_to_utf16be_with_errors(input.u32.data(), units, out16.data()); break; default: out32 = input.u32; r = {simdutf::SUCCESS, units}; break; } break; } if (r.error != simdutf::SUCCESS) return {int(r.error), r.count * src_unit}; if (dst_unit == 2) std::memcpy(output, out16.data(), r.count * 2); if (dst_unit == 4) { if (swap_utf32(dst)) { for (size_t i = 0; i < r.count; ++i) out32[i] = swap32(out32[i]); } std::memcpy(output, out32.data(), r.count * 4); } return {0, r.count * dst_unit}; } // Results cross into OCaml as one immediate integer, so that the hot stubs // neither allocate nor raise and can be declared [@@noalloc]: a count >= 0 on // success, or -(code + 16 * offset) on failure. Keep in sync with // Simdutf.status_error. intnat pack(outcome r) { if (r.code == 0) return intnat(r.count); // Only reachable with 31-bit OCaml integers and bigarrays beyond 64 MiB. constexpr size_t max_offset = size_t(Max_long) >> 4; const size_t offset = r.count < max_offset ? r.count : max_offset; return -(intnat(r.code) + intnat(offset << 4)); } // Like native_call, but reports C++ failures as status codes, since noalloc // stubs and blocking sections cannot raise OCaml exceptions. template outcome guarded(F f) noexcept { try { return f(); } catch (const std::bad_alloc &) { } catch (const std::length_error &) { } catch (const output_size_overflow &) { return {size_overflow, 0}; } return {out_of_memory, 0}; } // [release] stubs run without the runtime lock. They are only used on // bigarrays, whose data does not move. The input and output bigarrays are // rooted so that they cannot be finalized meanwhile, even if the caller drops // them. OCaml strings and bytes may move, so stubs reading or writing them // always hold the lock. template value run(value input, value output, F f) { if (!release) return Val_long(pack(guarded(f))); CAMLparam2(input, output); caml_enter_blocking_section(); const outcome r = guarded(f); caml_leave_blocking_section(); CAMLreturn(Val_long(pack(r))); } // Slices are bounds-checked in OCaml before reaching these stubs. const char *string_data(value s, value off) { return String_val(s) + Long_val(off); } char *bytes_data(value b, value off) { return reinterpret_cast(Bytes_val(b)) + Long_val(off); } char *bigstring_data(value b, value off, value len) { // Empty bigarrays can have a null data pointer. static char empty[1]; if (Long_val(len) == 0) return empty; return static_cast(Caml_ba_data_val(b)) + Long_val(off); } bool overlap(const char *input, size_t size, const char *output, size_t capacity) { if (size == 0 || capacity == 0) return false; const auto a = reinterpret_cast(input); const auto b = reinterpret_cast(output); return a <= b ? b - a < size : a - b < capacity; } int detect_bigstring(const char *data, size_t size) { // Upstream detection may reinterpret input as char16_t/char32_t. A sub-view // of a bigarray need not have that alignment. if (reinterpret_cast(data) % alignof(char32_t) == 0 || size == 0) return simdutf::detect_encodings(data, size); std::vector aligned((size + 3) / 4); std::memcpy(aligned.data(), data, size); return simdutf::detect_encodings( reinterpret_cast(aligned.data()), size); } outcome is_valid_outcome(int enc, const char *data, size_t size) { return {0, valid(enc, data, size)}; } outcome validate_ascii(const char *data, size_t size) { const auto r = simdutf::validate_ascii_with_errors(data, size); return {int(r.error), r.count}; } outcome is_ascii(const char *data, size_t size) { return {0, simdutf::validate_ascii(data, size)}; } outcome transcode_into(int src, int dst, const char *data, size_t size, char *output, size_t capacity) { if (overlap(data, size, output, capacity)) return {overlapping_slices, 0}; return convert(src, dst, data, size, output, capacity); } outcome base64_decoded_length(const char *data, size_t size) { return {0, simdutf::binary_length_from_base64(data, size)}; } outcome base64_encode_into(int options, const char *data, size_t size, char *output, size_t capacity) { if (overlap(data, size, output, capacity)) return {overlapping_slices, 0}; const auto opt = simdutf::base64_options(options); const size_t needed = checked_output_bytes(simdutf::base64_length_from_binary(size, opt)); if (needed > capacity) return {simdutf::OUTPUT_BUFFER_TOO_SMALL, 0}; return {0, simdutf::binary_to_base64(data, size, output, opt)}; } // [mode] packs the alphabet (bit 0), the last-chunk handling (bit 1), and // whether the caller guarantees the capacity (bit 2). Keep in sync with // Simdutf.Base64.decode_mode. outcome base64_decode_into(int mode, const char *data, size_t size, char *output, size_t capacity) { if (overlap(data, size, output, capacity)) return {overlapping_slices, 0}; // Upstream guarantees that binary_length_from_base64 can size the output for // any input. It is exact for valid input, including whitespace, but it scans // the input. The constant-time maximal length is never smaller, so capacity // at least that large needs no scan. const bool trusted = mode & 4; if (!trusted && capacity < simdutf::maximal_binary_length_from_base64(data, size) && simdutf::binary_length_from_base64(data, size) > capacity) return {simdutf::OUTPUT_BUFFER_TOO_SMALL, 0}; const auto r = simdutf::base64_to_binary_details( data, size, output, simdutf::base64_options(mode & 1), simdutf::last_chunk_handling_options((mode >> 1) & 1)); if (r.error != simdutf::SUCCESS) return {int(r.error), r.input_count}; return {0, r.output_count}; } // Searches return the offset of the first match within the slice, or the slice // length when there is none, as upstream returns the end pointer. // Upstream scans byte by byte up to 64-byte alignment and after the last full // 64-byte block, which dominates short slices: at 1 KiB and below, the C // library's memchr measured two to three times faster on AVX2. Longer slices // use upstream, whose speed does not depend on the C library. constexpr size_t find_memchr_below = 1024; outcome find_byte(const char *data, size_t size, char byte) { if (size < find_memchr_below) { const void *p = std::memchr(data, static_cast(byte), size); return {0, p ? size_t(static_cast(p) - data) : size}; } return {0, size_t(simdutf::find(data, data + size, byte) - data)}; } // [unit] is given in the byte order of [enc]. It is compared against code units // loaded in host order, so neither the input nor the result needs swapping. outcome find_code_unit(int enc, const char *data, size_t size, char16_t unit) { const bool swap = (enc == utf16be) != bool(SIMDUTF_IS_BIG_ENDIAN); const char16_t target = swap ? char16_t((unit >> 8) | (unit << 8)) : unit; const size_t units = size / 2; if (reinterpret_cast(data) % alignof(char16_t) == 0) { const auto *start = reinterpret_cast(data); return {0, size_t(simdutf::find(start, start + units, target) - start) * 2}; } // Unaligned input is searched through an aligned buffer, chunk by chunk. constexpr size_t chunk = 2048; char16_t buffer[chunk]; for (size_t done = 0; done < units;) { const size_t n = units - done < chunk ? units - done : chunk; std::memcpy(buffer, data + done * 2, n * 2); const size_t i = size_t(simdutf::find(buffer, buffer + n, target) - buffer); if (i < n) return {0, (done + i) * 2}; done += n; } return {0, units * 2}; } } // namespace // Each bigstring operation has a stub for [@@noalloc] use and a [_blocking] // variant that releases the runtime lock, for large inputs. #define BIGSTRING_STUBS(name, params, input, output, prologue, ...) \ CAMLprim value name params { \ prologue; \ return run(input, output, [&] { return __VA_ARGS__; }); \ } \ CAMLprim value name##_blocking params { \ prologue; \ return run(input, output, [&] { return __VA_ARGS__; }); \ } #define BYTECODE_STUB(name, ...) \ CAMLprim value name##_bytecode(value *args, int) { \ return name(__VA_ARGS__); \ } #define ARGS7 args[0], args[1], args[2], args[3], args[4], args[5], args[6] #define ARGS8 ARGS7, args[7] extern "C" { CAMLprim value ocaml_simdutf_version(value unit) { CAMLparam1(unit); CAMLreturn(caml_copy_string(SIMDUTF_VERSION)); } CAMLprim value ocaml_simdutf_implementation(value unit) { CAMLparam1(unit); // Force lazy runtime dispatch before querying its name. const bool initialized = simdutf::validate_ascii("", 0); (void)initialized; const auto name = simdutf::get_active_implementation()->name(); CAMLreturn(caml_alloc_initialized_string(name.size(), name.data())); } CAMLprim value ocaml_simdutf_detect(value input) { CAMLparam1(input); CAMLreturn(Val_int( simdutf::detect_encodings(String_val(input), caml_string_length(input)))); } /* String and bytes stubs. These are all [@@noalloc]: the OCaml heap cannot move while they run, so pointers taken from String_val stay valid. */ CAMLprim value ocaml_simdutf_validate(value enc, value input, value off, value len) { const char *data = string_data(input, off); return Val_long(pack(guarded([&] { return validate_or_count(Int_val(enc), data, Long_val(len), false); }))); } CAMLprim value ocaml_simdutf_is_valid(value enc, value input, value off, value len) { const char *data = string_data(input, off); return Val_long(pack( guarded([&] { return is_valid_outcome(Int_val(enc), data, Long_val(len)); }))); } CAMLprim value ocaml_simdutf_count(value enc, value input, value off, value len) { const char *data = string_data(input, off); return Val_long(pack(guarded([&] { return validate_or_count(Int_val(enc), data, Long_val(len), true); }))); } CAMLprim value ocaml_simdutf_validate_ascii(value input, value off, value len) { return Val_long(pack(validate_ascii(string_data(input, off), Long_val(len)))); } CAMLprim value ocaml_simdutf_is_ascii(value input, value off, value len) { return Val_bool(simdutf::validate_ascii(string_data(input, off), Long_val(len))); } CAMLprim value ocaml_simdutf_transcode_into_bytes(value src, value dst, value input, value off, value len, value output, value output_off, value output_len) { const char *data = string_data(input, off); char *dest = bytes_data(output, output_off); return Val_long(pack(guarded([&] { return transcode_into(Int_val(src), Int_val(dst), data, Long_val(len), dest, Long_val(output_len)); }))); } BYTECODE_STUB(ocaml_simdutf_transcode_into_bytes, ARGS8) CAMLprim value ocaml_simdutf_base64_encode(value options, value input) { CAMLparam2(options, input); CAMLlocal1(output); const size_t size = caml_string_length(input); if ((size + 2) / 3 > max_string_length / 4) caml_invalid_argument("Simdutf: output exceeds maximum string length"); const auto opt = simdutf::base64_options(Int_val(options)); const size_t capacity = simdutf::base64_length_from_binary(size, opt); output = caml_alloc_string(capacity); // Reacquire all OCaml pointers after the allocation above. simdutf::binary_to_base64(String_val(input), size, reinterpret_cast(Bytes_val(output)), opt); CAMLreturn(output); } CAMLprim value ocaml_simdutf_base64_decoded_length(value input, value off, value len) { return Val_long(simdutf::binary_length_from_base64(string_data(input, off), Long_val(len))); } CAMLprim value ocaml_simdutf_base64_decode_into(value mode, value input, value off, value len, value output, value output_off, value output_len) { return Val_long(pack(base64_decode_into( Int_val(mode), string_data(input, off), Long_val(len), bytes_data(output, output_off), Long_val(output_len)))); } BYTECODE_STUB(ocaml_simdutf_base64_decode_into, ARGS7) CAMLprim value ocaml_simdutf_find(value input, value off, value len, value byte) { return Val_long(pack(find_byte(string_data(input, off), Long_val(len), char(Int_val(byte))))); } CAMLprim value ocaml_simdutf_find_code_unit(value enc, value unit, value input, value off, value len) { return Val_long(pack(find_code_unit(Int_val(enc), string_data(input, off), Long_val(len), char16_t(Int_val(unit))))); } /* Copies between strings and bigstrings, [@@noalloc]. */ CAMLprim value ocaml_simdutf_blit_string_to_bigstring(value src, value src_off, value dst, value dst_off, value len) { if (Long_val(len) > 0) std::memcpy(bigstring_data(dst, dst_off, len), string_data(src, src_off), Long_val(len)); return Val_unit; } CAMLprim value ocaml_simdutf_blit_bigstring_to_bytes(value src, value src_off, value dst, value dst_off, value len) { if (Long_val(len) > 0) std::memcpy(bytes_data(dst, dst_off), bigstring_data(src, src_off, len), Long_val(len)); return Val_unit; } /* Bigstring stubs. */ BIGSTRING_STUBS(ocaml_simdutf_bigstring_validate, (value enc, value input, value off, value len), input, Val_unit, const char *data = bigstring_data(input, off, len); const int e = Int_val(enc); const size_t size = Long_val(len), validate_or_count(e, data, size, false)) BIGSTRING_STUBS(ocaml_simdutf_bigstring_is_valid, (value enc, value input, value off, value len), input, Val_unit, const char *data = bigstring_data(input, off, len); const int e = Int_val(enc); const size_t size = Long_val(len), is_valid_outcome(e, data, size)) BIGSTRING_STUBS(ocaml_simdutf_bigstring_count, (value enc, value input, value off, value len), input, Val_unit, const char *data = bigstring_data(input, off, len); const int e = Int_val(enc); const size_t size = Long_val(len), validate_or_count(e, data, size, true)) BIGSTRING_STUBS(ocaml_simdutf_bigstring_validate_ascii, (value input, value off, value len), input, Val_unit, const char *data = bigstring_data(input, off, len); const size_t size = Long_val(len), validate_ascii(data, size)) BIGSTRING_STUBS(ocaml_simdutf_bigstring_is_ascii, (value input, value off, value len), input, Val_unit, const char *data = bigstring_data(input, off, len); const size_t size = Long_val(len), is_ascii(data, size)) BIGSTRING_STUBS(ocaml_simdutf_bigstring_transcoded_length, (value src, value dst, value input, value off, value len), input, Val_unit, const char *data = bigstring_data(input, off, len); const int s = Int_val(src); const int d = Int_val(dst); const size_t size = Long_val(len), transcoded_length(s, d, data, size)) BIGSTRING_STUBS(ocaml_simdutf_bigstring_transcode_into, (value src, value dst, value input, value off, value len, value output, value output_off, value output_len), input, output, const char *data = bigstring_data(input, off, len); char *dest = bigstring_data(output, output_off, output_len); const int s = Int_val(src); const int d = Int_val(dst); const size_t size = Long_val(len); const size_t capacity = Long_val(output_len), transcode_into(s, d, data, size, dest, capacity)) BYTECODE_STUB(ocaml_simdutf_bigstring_transcode_into, ARGS8) BYTECODE_STUB(ocaml_simdutf_bigstring_transcode_into_blocking, ARGS8) // Searches are short and retain the runtime lock, so there is no blocking // variant. CAMLprim value ocaml_simdutf_bigstring_find(value input, value off, value len, value byte) { return Val_long(pack(find_byte(bigstring_data(input, off, len), Long_val(len), char(Int_val(byte))))); } BIGSTRING_STUBS(ocaml_simdutf_bigstring_find_code_unit, (value enc, value unit, value input, value off, value len), input, Val_unit, const char *data = bigstring_data(input, off, len); const int e = Int_val(enc); const char16_t u = char16_t(Int_val(unit)); const size_t size = Long_val(len), find_code_unit(e, data, size, u)) CAMLprim value ocaml_simdutf_bigstring_detect(value input, value off, value len) { CAMLparam3(input, off, len); const char *data = bigstring_data(input, off, len); const int mask = native_call([&] { return detect_bigstring(data, Long_val(len)); }); CAMLreturn(Val_int(mask)); } CAMLprim value ocaml_simdutf_bigstring_base64_encoded_length(value options, value len) { CAMLparam2(options, len); if (Long_val(len) < 0) caml_invalid_argument("Simdutf.Bigstring.Base64: negative input length"); const size_t size = native_call([&] { return checked_output_bytes(simdutf::base64_length_from_binary( Long_val(len), simdutf::base64_options(Int_val(options)))); }); CAMLreturn(Val_long(size)); } BIGSTRING_STUBS(ocaml_simdutf_bigstring_base64_decoded_length, (value input, value off, value len), input, Val_unit, const char *data = bigstring_data(input, off, len); const size_t size = Long_val(len), base64_decoded_length(data, size)) BIGSTRING_STUBS(ocaml_simdutf_bigstring_base64_encode_into, (value options, value input, value off, value len, value output, value output_off, value output_len), input, output, const char *data = bigstring_data(input, off, len); char *dest = bigstring_data(output, output_off, output_len); const int opt = Int_val(options); const size_t size = Long_val(len); const size_t capacity = Long_val(output_len), base64_encode_into(opt, data, size, dest, capacity)) BYTECODE_STUB(ocaml_simdutf_bigstring_base64_encode_into, ARGS7) BYTECODE_STUB(ocaml_simdutf_bigstring_base64_encode_into_blocking, ARGS7) BIGSTRING_STUBS(ocaml_simdutf_bigstring_base64_decode_into, (value mode, value input, value off, value len, value output, value output_off, value output_len), input, output, const char *data = bigstring_data(input, off, len); char *dest = bigstring_data(output, output_off, output_len); const int m = Int_val(mode); const size_t size = Long_val(len); const size_t capacity = Long_val(output_len), base64_decode_into(m, data, size, dest, capacity)) BYTECODE_STUB(ocaml_simdutf_bigstring_base64_decode_into, ARGS7) BYTECODE_STUB(ocaml_simdutf_bigstring_base64_decode_into_blocking, ARGS7) // Writes OCaml bytes, so there is no blocking variant. CAMLprim value ocaml_simdutf_bigstring_base64_decode_to_bytes( value mode, value input, value off, value len, value output, value output_off, value output_len) { return Val_long(pack(base64_decode_into( Int_val(mode), bigstring_data(input, off, len), Long_val(len), bytes_data(output, output_off), Long_val(output_len)))); } BYTECODE_STUB(ocaml_simdutf_bigstring_base64_decode_to_bytes, ARGS7) } // extern "C"