Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 59 additions & 6 deletions src/browser/file/Blob.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -88,7 +108,6 @@ fn writeBlobParts(
}

// TODO: Windows support.
// TODO: Vector search.

// Linux & Unix.
// Both Firefox and Chrome implement it as such:
Expand All @@ -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) {
Expand All @@ -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;
}
}

Expand All @@ -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]);
}
}

Expand Down
36 changes: 36 additions & 0 deletions src/tests/file/blob.html
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
}
</script>