Skip to content

Commit 47ceabc

Browse files
authored
Merge pull request #1195 from lightpanda-io/nikneym/blob-simd
2 parents dc4927d + 74a5438 commit 47ceabc

File tree

2 files changed

+95
-6
lines changed

2 files changed

+95
-6
lines changed

src/browser/file/Blob.zig

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,26 @@ pub fn constructor(
7272
return .{ .slice = "", .mime = mime };
7373
}
7474

75+
const largest_vector = @max(std.simd.suggestVectorLength(u8) orelse 1, 8);
76+
/// Array of possible vector sizes for the current arch in decrementing order.
77+
/// We may move this to some file for SIMD helpers in the future.
78+
const vector_sizes = blk: {
79+
// Required for length calculation.
80+
var n: usize = largest_vector;
81+
var total: usize = 0;
82+
while (n != 2) : (n /= 2) total += 1;
83+
// Populate an array with vector sizes.
84+
n = largest_vector;
85+
var i: usize = 0;
86+
var items: [total]usize = undefined;
87+
while (n != 2) : (n /= 2) {
88+
defer i += 1;
89+
items[i] = n;
90+
}
91+
92+
break :blk items;
93+
};
94+
7595
/// Writes blob parts to given `Writer` with desired endings.
7696
fn writeBlobParts(
7797
writer: *Writer,
@@ -88,7 +108,6 @@ fn writeBlobParts(
88108
}
89109

90110
// TODO: Windows support.
91-
// TODO: Vector search.
92111

93112
// Linux & Unix.
94113
// Both Firefox and Chrome implement it as such:
@@ -108,10 +127,44 @@ fn writeBlobParts(
108127
// ```
109128
scan_parts: for (blob_parts) |part| {
110129
var end: usize = 0;
111-
var start = end;
130+
131+
inline for (vector_sizes) |vector_len| {
132+
const Vec = @Vector(vector_len, u8);
133+
134+
while (end + vector_len <= part.len) : (end += vector_len) {
135+
const cr: Vec = @splat('\r');
136+
// Load chunk as vectors.
137+
const slice = part[end..][0..vector_len];
138+
const chunk: Vec = slice.*;
139+
// Look for CR.
140+
const match = chunk == cr;
141+
142+
// Create a bitset out of match vector.
143+
const bitset = std.bit_set.IntegerBitSet(vector_len){
144+
.mask = @bitCast(@intFromBool(match)),
145+
};
146+
147+
var iter = bitset.iterator(.{});
148+
var relative_start: usize = 0;
149+
while (iter.next()) |index| {
150+
_ = try writer.writeVec(&.{ slice[relative_start..index], "\n" });
151+
152+
if (index + 1 != slice.len and slice[index + 1] == '\n') {
153+
relative_start = index + 2;
154+
} else {
155+
relative_start = index + 1;
156+
}
157+
}
158+
159+
_ = try writer.writeVec(&.{slice[relative_start..]});
160+
}
161+
}
162+
163+
// Scalar scan fallback.
164+
var relative_start: usize = end;
112165
while (end < part.len) {
113166
if (part[end] == '\r') {
114-
_ = try writer.writeVec(&.{ part[start..end], "\n" });
167+
_ = try writer.writeVec(&.{ part[relative_start..end], "\n" });
115168

116169
// Part ends with CR. We can continue to next part.
117170
if (end + 1 == part.len) {
@@ -120,9 +173,9 @@ fn writeBlobParts(
120173

121174
// If next char is LF, skip it too.
122175
if (part[end + 1] == '\n') {
123-
start = end + 2;
176+
relative_start = end + 2;
124177
} else {
125-
start = end + 1;
178+
relative_start = end + 1;
126179
}
127180
}
128181

@@ -132,7 +185,7 @@ fn writeBlobParts(
132185
// Write the remaining. We get this in such situations:
133186
// `the quick brown\rfox`
134187
// `the quick brown\r\nfox`
135-
try writer.writeAll(part[start..end]);
188+
try writer.writeAll(part[relative_start..end]);
136189
}
137190
}
138191

src/tests/file/blob.html

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,40 @@
8686
testing.expectEqual(expected, result);
8787
});
8888
}
89+
90+
// Test for SIMD.
91+
{
92+
const parts = [
93+
"\rThe opened package\r\nof potato\nchi\rps",
94+
"held the\r\nanswer to the\r mystery. Both det\rectives looke\r\rd\r",
95+
"\rat it but failed to realize\nit was\r\nthe\rkey\r\n",
96+
"\r\nto solve the \rcrime.\r"
97+
];
98+
99+
const blob = new Blob(parts, { type: "text/html", endings: "native" });
100+
testing.expectEqual(161, blob.size);
101+
testing.expectEqual("text/html", blob.type);
102+
testing.async(blob.bytes(), result => {
103+
const expected = new Uint8Array([10, 84, 104, 101, 32, 111, 112, 101, 110,
104+
101, 100, 32, 112, 97, 99, 107, 97, 103,
105+
101, 10, 111, 102, 32, 112, 111, 116, 97,
106+
116, 111, 10, 99, 104, 105, 10, 112, 115,
107+
104, 101, 108, 100, 32, 116, 104, 101, 10,
108+
97, 110, 115, 119, 101, 114, 32, 116, 111,
109+
32, 116, 104, 101, 10, 32, 109, 121, 115,
110+
116, 101, 114, 121, 46, 32, 66, 111, 116,
111+
104, 32, 100, 101, 116, 10, 101, 99, 116,
112+
105, 118, 101, 115, 32, 108, 111, 111, 107,
113+
101, 10, 10, 100, 10, 10, 97, 116, 32, 105,
114+
116, 32, 98, 117, 116, 32, 102, 97, 105, 108,
115+
101, 100, 32, 116, 111, 32, 114, 101, 97,
116+
108, 105, 122, 101, 10, 105, 116, 32, 119, 97,
117+
115, 10, 116, 104, 101, 10, 107, 101, 121,
118+
10, 10, 116, 111, 32, 115, 111, 108, 118, 101,
119+
32, 116, 104, 101, 32, 10, 99, 114, 105, 109,
120+
101, 46, 10]);
121+
testing.expectEqual(true, result instanceof Uint8Array);
122+
testing.expectEqual(expected, result);
123+
});
124+
}
89125
</script>

0 commit comments

Comments
 (0)