aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/simd/vt.cpp
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-05-07 18:29:21 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-05-07 18:29:21 +0300
commitdcfe11a7588fcd42471159453810abc9b7720a57 (patch)
treec32a77266028e73ca2382b96c96f8438a5fcd621 /vendor/libghostty-vt/src/simd/vt.cpp
parent2dfed860103c4e4505ac7f25def9fe37da1e1e6a (diff)
build: update libghostty-vt vendor
vendor ghostty 063ac3e and remove the musl libc++ link workaround. expose the new selection formatter and hyperlink uri apis for follow-up rendering work. add manual artifact validation for static linux builds.
Diffstat (limited to 'vendor/libghostty-vt/src/simd/vt.cpp')
-rw-r--r--vendor/libghostty-vt/src/simd/vt.cpp213
1 files changed, 180 insertions, 33 deletions
diff --git a/vendor/libghostty-vt/src/simd/vt.cpp b/vendor/libghostty-vt/src/simd/vt.cpp
index d8133abf..5bf4147d 100644
--- a/vendor/libghostty-vt/src/simd/vt.cpp
+++ b/vendor/libghostty-vt/src/simd/vt.cpp
@@ -5,8 +5,6 @@
#include <hwy/highway.h>
#include <simdutf.h>
-#include <utf8.h>
-#include <vector>
#include <simd/index_of.h>
#include <simd/vt.h>
@@ -19,12 +17,144 @@ namespace hn = hwy::HWY_NAMESPACE;
using T = uint8_t;
+// Compute the length of the maximal subpart of an ill-formed UTF-8
+// subsequence starting at p[0], per Unicode Table 3-7 and the W3C
+// "U+FFFD Substitution of Maximal Subparts" algorithm.
+//
+// The maximal subpart is the longest initial subsequence that is either:
+// (a) the start of a well-formed sequence, or
+// (b) a single byte.
+// Each maximal subpart maps to exactly one U+FFFD.
+static size_t MaximalSubpart(const unsigned char* p, size_t len) {
+ if (len == 0) return 0;
+
+ unsigned char b0 = p[0];
+
+ // Continuation bytes (80-BF), overlong leads (C0-C1), or invalid (F5-FF):
+ // each is its own maximal subpart of length 1.
+ if (b0 < 0xC2 || b0 > 0xF4) return 1;
+
+ // Determine the expected sequence length and the valid range for each
+ // continuation byte per Unicode Table 3-7.
+ size_t seq_len;
+ unsigned char lo[3], hi[3];
+
+ if (b0 <= 0xDF) {
+ seq_len = 2;
+ lo[0] = 0x80; hi[0] = 0xBF;
+ } else if (b0 == 0xE0) {
+ seq_len = 3;
+ lo[0] = 0xA0; hi[0] = 0xBF;
+ lo[1] = 0x80; hi[1] = 0xBF;
+ } else if (b0 <= 0xEC) {
+ seq_len = 3;
+ lo[0] = 0x80; hi[0] = 0xBF;
+ lo[1] = 0x80; hi[1] = 0xBF;
+ } else if (b0 == 0xED) {
+ seq_len = 3;
+ lo[0] = 0x80; hi[0] = 0x9F;
+ lo[1] = 0x80; hi[1] = 0xBF;
+ } else if (b0 <= 0xEF) {
+ seq_len = 3;
+ lo[0] = 0x80; hi[0] = 0xBF;
+ lo[1] = 0x80; hi[1] = 0xBF;
+ } else if (b0 == 0xF0) {
+ seq_len = 4;
+ lo[0] = 0x90; hi[0] = 0xBF;
+ lo[1] = 0x80; hi[1] = 0xBF;
+ lo[2] = 0x80; hi[2] = 0xBF;
+ } else if (b0 <= 0xF3) {
+ seq_len = 4;
+ lo[0] = 0x80; hi[0] = 0xBF;
+ lo[1] = 0x80; hi[1] = 0xBF;
+ lo[2] = 0x80; hi[2] = 0xBF;
+ } else { // b0 == 0xF4
+ seq_len = 4;
+ lo[0] = 0x80; hi[0] = 0x8F;
+ lo[1] = 0x80; hi[1] = 0xBF;
+ lo[2] = 0x80; hi[2] = 0xBF;
+ }
+
+ // Check continuation bytes against their specific valid ranges.
+ // The maximal subpart extends as far as bytes match.
+ size_t valid = 1; // lead byte counts
+ for (size_t i = 0; i < seq_len - 1 && valid < len; i++) {
+ unsigned char cb = p[valid];
+ if (cb < lo[i] || cb > hi[i]) break;
+ valid++;
+ }
+
+ // If we matched all bytes, the sequence is structurally valid
+ // (shouldn't happen since we're called on an error), but cap
+ // to avoid skipping a valid sequence.
+ if (valid == seq_len) return valid;
+
+ return valid;
+}
+
+// Trim trailing bytes that form a valid-but-incomplete UTF-8 sequence.
+// Only trims sequences whose bytes so far match Table 3-7 ranges (i.e.,
+// truly partial sequences that could be completed by future input).
+// Invalid lead bytes (C0, C1, F5-FF) or mismatched continuations are NOT
+// trimmed — they will be handled as errors by DecodeUTF8.
+static size_t TrimValidPartialUTF8(const uint8_t* input, size_t len) {
+ if (len == 0) return 0;
+
+ // Find the start of a potential trailing partial sequence by scanning
+ // backwards from the end. We look for a lead byte (C2-F4) that could
+ // start a multi-byte sequence, possibly followed by continuation bytes.
+ //
+ // We check up to the last 4 bytes (max UTF-8 sequence length).
+ size_t check_start = len > 4 ? len - 4 : 0;
+ for (size_t pos = len; pos > check_start; pos--) {
+ unsigned char b = input[pos - 1];
+
+ // Skip continuation bytes — they might belong to the partial sequence.
+ if ((b & 0xC0) == 0x80) continue;
+
+ // Found a non-continuation byte. Only valid multi-byte leads (C2-F4)
+ // can start a partial sequence worth trimming. Anything else (ASCII,
+ // C0, C1, F5-FF) should be consumed by DecodeUTF8.
+ if (b < 0xC2 || b > 0xF4) return len;
+
+ // Determine expected sequence length from the lead byte.
+ size_t expected;
+ if (b <= 0xDF)
+ expected = 2;
+ else if (b <= 0xEF)
+ expected = 3;
+ else
+ expected = 4;
+
+ size_t seq_remaining = len - (pos - 1);
+
+ // If we have all expected bytes, the sequence is complete (not partial).
+ if (seq_remaining >= expected) return len;
+
+ // Check if the trailing bytes form a valid prefix using MaximalSubpart.
+ const unsigned char* seq_start = input + pos - 1;
+ size_t subpart = MaximalSubpart(seq_start, seq_remaining);
+
+ // Only trim if ALL trailing bytes are part of the valid prefix
+ // (the sequence is valid-so-far but incomplete).
+ if (subpart == seq_remaining) {
+ return pos - 1;
+ }
+
+ // The sequence is ill-formed, don't trim — let DecodeUTF8 handle it.
+ return len;
+ }
+
+ return len;
+}
+
// Decode the UTF-8 text in input into output. Returns the number of decoded
// characters. This function assumes output is large enough.
//
// This function handles malformed UTF-8 sequences by inserting a
-// replacement character (U+FFFD) and continuing to decode. This function
-// will consume the entire input no matter what.
+// replacement character (U+FFFD) following the W3C/Unicode "U+FFFD
+// Substitution of Maximal Subparts" algorithm and continuing to decode.
+// This function will consume the entire input no matter what.
size_t DecodeUTF8(const uint8_t* HWY_RESTRICT input,
size_t count,
char32_t* output) {
@@ -34,27 +164,38 @@ size_t DecodeUTF8(const uint8_t* HWY_RESTRICT input,
return 0;
}
- // Assume no errors for fast path.
- const size_t decoded = simdutf::convert_utf8_to_utf32(
- reinterpret_cast<const char*>(input), count, output);
- if (decoded > 0) {
- return decoded;
+ // Decode UTF-8 to UTF-32, replacing invalid sequences with U+FFFD.
+ const char* in = reinterpret_cast<const char*>(input);
+ size_t remaining = count;
+ char32_t* out = output;
+ while (remaining > 0) {
+ auto r = simdutf::convert_utf8_to_utf32_with_errors(in, remaining, out);
+
+ // If the decode was a full success then we're done!
+ if (r.error == simdutf::SUCCESS) {
+ out += r.count;
+ break;
+ }
+
+ // On error, r.count is the input byte position of the error.
+ // The output buffer is already written up to that point, but
+ // we need count_utf8 to find how many char32_t that produced.
+ out += simdutf::count_utf8(in, r.count);
+
+ // Compute the maximal subpart at the error position and emit
+ // a single U+FFFD for it.
+ const unsigned char* err_pos =
+ reinterpret_cast<const unsigned char*>(in + r.count);
+ size_t err_remaining = remaining - r.count;
+ size_t skip = r.count + MaximalSubpart(err_pos, err_remaining);
+
+ *out++ = 0xFFFD;
+
+ in += skip;
+ remaining -= skip;
}
- // Errors in the UTF input, take a slow path and do a decode with
- // replacement (with U+FFFD). Note that simdutf doesn't have a
- // decode with replacement API:
- // https://github.com/simdutf/simdutf/issues/147
- //
- // Because of this, we use a separate library with heap allocation
- // that is much, much slower (the allocation is slower, the algorithm
- // is slower, etc.) This is just so we have something that works.
- // I want to replace this.
- std::vector<char> replacement_result;
- utf8::replace_invalid(input, input + count,
- std::back_inserter(replacement_result), 0xFFFD);
- return DecodeUTF8(reinterpret_cast<const uint8_t*>(replacement_result.data()),
- replacement_result.size(), output);
+ return static_cast<size_t>(out - output);
}
/// Decode the UTF-8 text in input into output until an escape
@@ -86,16 +227,16 @@ size_t DecodeUTF8UntilControlSeqImpl(D d,
// If we don't have any escapes we keep going. We want to accumulate
// the largest possible valid UTF-8 sequence before decoding.
// TODO(mitchellh): benchmark this vs decoding every time
- const auto esc_idx = IndexOfChunk(d, esc_vec, input_vec);
- if (!esc_idx) {
+ const size_t esc_idx = IndexOfChunk(d, esc_vec, input_vec);
+ if (esc_idx == kNotFound) {
continue;
}
// We have an ESC char, decode up to this point. We start by assuming
// a valid UTF-8 sequence and slow-path into error handling if we find
// an invalid sequence.
- *output_count = DecodeUTF8(input, i + esc_idx.value(), output);
- return i + esc_idx.value();
+ *output_count = DecodeUTF8(input, i + esc_idx, output);
+ return i + esc_idx;
}
// If we have leftover input then we decode it one byte at a time (slow!)
@@ -106,21 +247,27 @@ size_t DecodeUTF8UntilControlSeqImpl(D d,
const hn::Vec<D1> esc1 = Set(d1, hn::GetLane(esc_vec));
for (; i < count; ++i) {
const hn::Vec<D1> input_vec = hn::LoadU(d1, input + i);
- const auto esc_idx = IndexOfChunk(d1, esc1, input_vec);
- if (!esc_idx) {
+ const size_t esc_idx = IndexOfChunk(d1, esc1, input_vec);
+ if (esc_idx == kNotFound) {
continue;
}
- *output_count = DecodeUTF8(input, i + esc_idx.value(), output);
- return i + esc_idx.value();
+ *output_count = DecodeUTF8(input, i + esc_idx, output);
+ return i + esc_idx;
}
}
// If we reached this point, its possible for our input to have an
// incomplete sequence because we're consuming the full input. We need
// to trim any incomplete sequences from the end of the input.
- const size_t trimmed_len =
- simdutf::trim_partial_utf8(reinterpret_cast<const char*>(input), i);
+ //
+ // We use our own trim instead of simdutf::trim_partial_utf8 because
+ // we only want to trim sequences that are valid-so-far (true partial
+ // sequences that may be completed by future input). Invalid bytes
+ // like C0, C1, F5-FF should NOT be trimmed — they should be passed
+ // through to DecodeUTF8 which will replace them with U+FFFD per the
+ // maximal subpart algorithm.
+ const size_t trimmed_len = TrimValidPartialUTF8(input, i);
*output_count = DecodeUTF8(input, trimmed_len, output);
return trimmed_len;
}