diff --git a/src/browser/file/Blob.zig b/src/browser/file/Blob.zig index 15f64681f..cfcbedf26 100644 --- a/src/browser/file/Blob.zig +++ b/src/browser/file/Blob.zig @@ -72,6 +72,26 @@ pub fn constructor( return .{ .slice = "", .mime = mime }; } +const largest_vector = @max(std.simd.suggestVectorLength(u8) orelse 1, 8); +/// Array of possible vector sizes for the current arch in decrementing order. +/// We may move this to some file for SIMD helpers in the future. +const vector_sizes = blk: { + // Required for length calculation. + var n: usize = largest_vector; + var total: usize = 0; + while (n != 2) : (n /= 2) total += 1; + // Populate an array with vector sizes. + n = largest_vector; + var i: usize = 0; + var items: [total]usize = undefined; + while (n != 2) : (n /= 2) { + defer i += 1; + items[i] = n; + } + + break :blk items; +}; + /// Writes blob parts to given `Writer` with desired endings. fn writeBlobParts( writer: *Writer, @@ -88,7 +108,6 @@ fn writeBlobParts( } // TODO: Windows support. - // TODO: Vector search. // Linux & Unix. // Both Firefox and Chrome implement it as such: @@ -108,10 +127,44 @@ fn writeBlobParts( // ``` scan_parts: for (blob_parts) |part| { var end: usize = 0; - var start = end; + + inline for (vector_sizes) |vector_len| { + const Vec = @Vector(vector_len, u8); + + while (end + vector_len <= part.len) : (end += vector_len) { + const cr: Vec = @splat('\r'); + // Load chunk as vectors. + const slice = part[end..][0..vector_len]; + const chunk: Vec = slice.*; + // Look for CR. + const match = chunk == cr; + + // Create a bitset out of match vector. + const bitset = std.bit_set.IntegerBitSet(vector_len){ + .mask = @bitCast(@intFromBool(match)), + }; + + var iter = bitset.iterator(.{}); + var relative_start: usize = 0; + while (iter.next()) |index| { + _ = try writer.writeVec(&.{ slice[relative_start..index], "\n" }); + + if (index + 1 != slice.len and slice[index + 1] == '\n') { + relative_start = index + 2; + } else { + relative_start = index + 1; + } + } + + _ = try writer.writeVec(&.{slice[relative_start..]}); + } + } + + // Scalar scan fallback. + var relative_start: usize = end; while (end < part.len) { if (part[end] == '\r') { - _ = try writer.writeVec(&.{ part[start..end], "\n" }); + _ = try writer.writeVec(&.{ part[relative_start..end], "\n" }); // Part ends with CR. We can continue to next part. if (end + 1 == part.len) { @@ -120,9 +173,9 @@ fn writeBlobParts( // If next char is LF, skip it too. if (part[end + 1] == '\n') { - start = end + 2; + relative_start = end + 2; } else { - start = end + 1; + relative_start = end + 1; } } @@ -132,7 +185,7 @@ fn writeBlobParts( // Write the remaining. We get this in such situations: // `the quick brown\rfox` // `the quick brown\r\nfox` - try writer.writeAll(part[start..end]); + try writer.writeAll(part[relative_start..end]); } } diff --git a/src/tests/file/blob.html b/src/tests/file/blob.html index 754861a31..343fd32be 100644 --- a/src/tests/file/blob.html +++ b/src/tests/file/blob.html @@ -86,4 +86,40 @@ testing.expectEqual(expected, result); }); } + + // Test for SIMD. + { + const parts = [ + "\rThe opened package\r\nof potato\nchi\rps", + "held the\r\nanswer to the\r mystery. Both det\rectives looke\r\rd\r", + "\rat it but failed to realize\nit was\r\nthe\rkey\r\n", + "\r\nto solve the \rcrime.\r" + ]; + + const blob = new Blob(parts, { type: "text/html", endings: "native" }); + testing.expectEqual(161, blob.size); + testing.expectEqual("text/html", blob.type); + testing.async(blob.bytes(), result => { + const expected = new Uint8Array([10, 84, 104, 101, 32, 111, 112, 101, 110, + 101, 100, 32, 112, 97, 99, 107, 97, 103, + 101, 10, 111, 102, 32, 112, 111, 116, 97, + 116, 111, 10, 99, 104, 105, 10, 112, 115, + 104, 101, 108, 100, 32, 116, 104, 101, 10, + 97, 110, 115, 119, 101, 114, 32, 116, 111, + 32, 116, 104, 101, 10, 32, 109, 121, 115, + 116, 101, 114, 121, 46, 32, 66, 111, 116, + 104, 32, 100, 101, 116, 10, 101, 99, 116, + 105, 118, 101, 115, 32, 108, 111, 111, 107, + 101, 10, 10, 100, 10, 10, 97, 116, 32, 105, + 116, 32, 98, 117, 116, 32, 102, 97, 105, 108, + 101, 100, 32, 116, 111, 32, 114, 101, 97, + 108, 105, 122, 101, 10, 105, 116, 32, 119, 97, + 115, 10, 116, 104, 101, 10, 107, 101, 121, + 10, 10, 116, 111, 32, 115, 111, 108, 118, 101, + 32, 116, 104, 101, 32, 10, 99, 114, 105, 109, + 101, 46, 10]); + testing.expectEqual(true, result instanceof Uint8Array); + testing.expectEqual(expected, result); + }); + }