Skip to content

Commit 6d6c1e6

Browse files
rmahdavV8 LUCI CQ
authored andcommitted
[base64] Add toHex and setFromHex paths for SharedArrayBuffers
This CL, adds a path to make sure we have an atomic read in toHex and atomic write in setFromHex when the TypedArray is backed with a SharedArrayBuffer. Bug: 42204568 Change-Id: Ib62ac7e6c6c89cb76bf150056bc16fb1b67e2f0f Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/6485466 Reviewed-by: Shu-yu Guo <[email protected]> Commit-Queue: Rezvan Mahdavi Hezaveh <[email protected]> Cr-Commit-Position: refs/heads/main@{#99900}
1 parent 277d16f commit 6d6c1e6

5 files changed

Lines changed: 186 additions & 38 deletions

File tree

src/builtins/builtins-typed-array.cc

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -888,14 +888,14 @@ BUILTIN(Uint8ArrayFromHex) {
888888
base::Vector<const uint8_t> input_vector =
889889
input_content.ToOneByteVector();
890890
result = ArrayBufferFromHex(
891-
input_vector, static_cast<uint8_t*>(buffer->backing_store()),
892-
output_length);
891+
input_vector, /*is_shared*/ false,
892+
static_cast<uint8_t*>(buffer->backing_store()), output_length);
893893
} else {
894894
base::Vector<const base::uc16> input_vector =
895895
input_content.ToUC16Vector();
896896
result = ArrayBufferFromHex(
897-
input_vector, static_cast<uint8_t*>(buffer->backing_store()),
898-
output_length);
897+
input_vector, /*is_shared*/ false,
898+
static_cast<uint8_t*>(buffer->backing_store()), output_length);
899899
}
900900
}
901901

@@ -964,11 +964,6 @@ BUILTIN(Uint8ArrayPrototypeSetFromHex) {
964964
size_t output_length = (input_length / 2);
965965
output_length = std::min(output_length, array_length);
966966

967-
// TODO(rezvan): Add path for typed arrays backed by SharedArrayBuffer
968-
if (uint8array->buffer()->is_shared()) {
969-
UNIMPLEMENTED();
970-
}
971-
972967
// 7. Let result be FromHex(string, byteLength).
973968
// 8. Let bytes be result.[[Bytes]].
974969
// 9. Let written be the length of bytes.
@@ -984,15 +979,15 @@ BUILTIN(Uint8ArrayPrototypeSetFromHex) {
984979
if (input_content.IsOneByte()) {
985980
base::Vector<const uint8_t> input_vector =
986981
input_content.ToOneByteVector();
987-
result = ArrayBufferFromHex(input_vector,
988-
static_cast<uint8_t*>(uint8array->DataPtr()),
989-
output_length);
982+
result = ArrayBufferFromHex(
983+
input_vector, uint8array->buffer()->is_shared(),
984+
static_cast<uint8_t*>(uint8array->DataPtr()), output_length);
990985
} else {
991986
base::Vector<const base::uc16> input_vector =
992987
input_content.ToUC16Vector();
993-
result = ArrayBufferFromHex(input_vector,
994-
static_cast<uint8_t*>(uint8array->DataPtr()),
995-
output_length);
988+
result = ArrayBufferFromHex(
989+
input_vector, uint8array->buffer()->is_shared(),
990+
static_cast<uint8_t*>(uint8array->DataPtr()), output_length);
996991
}
997992
}
998993

@@ -1062,7 +1057,8 @@ BUILTIN(Uint8ArrayPrototypeToHex) {
10621057
// b. Set hex to StringPad(hex, 2, "0", start).
10631058
// c. Set out to the string-concatenation of out and hex.
10641059
// 6. Return out.
1065-
return Uint8ArrayToHex(bytes, length, output);
1060+
return Uint8ArrayToHex(bytes, length, uint8array->buffer()->is_shared(),
1061+
output);
10661062
}
10671063

10681064
} // namespace internal

src/objects/simd.cc

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -458,16 +458,38 @@ char NibbleToHex(uint8_t nibble) {
458458
return c + (mask & correction);
459459
}
460460

461+
void PerformNibbleToHexAndWriteIntoStringOutPut(
462+
uint8_t byte, int index, DirectHandle<SeqOneByteString> string_output) {
463+
uint8_t high = byte >> 4;
464+
uint8_t low = byte & 0x0F;
465+
466+
string_output->SeqOneByteStringSet(index++, NibbleToHex(high));
467+
string_output->SeqOneByteStringSet(index, NibbleToHex(low));
468+
}
469+
461470
void Uint8ArrayToHexSlow(const char* bytes, size_t length,
462471
DirectHandle<SeqOneByteString> string_output) {
463472
int index = 0;
464473
for (size_t i = 0; i < length; i++) {
465474
uint8_t byte = bytes[i];
466-
uint8_t high = byte >> 4;
467-
uint8_t low = byte & 0x0F;
475+
PerformNibbleToHexAndWriteIntoStringOutPut(byte, index, string_output);
476+
index += 2;
477+
}
478+
}
468479

469-
string_output->SeqOneByteStringSet(index++, NibbleToHex(high));
470-
string_output->SeqOneByteStringSet(index++, NibbleToHex(low));
480+
void AtomicUint8ArrayToHexSlow(const char* bytes, size_t length,
481+
DirectHandle<SeqOneByteString> string_output) {
482+
int index = 0;
483+
// std::atomic_ref<T> must not have a const T, see
484+
// https://cplusplus.github.io/LWG/issue3508
485+
// we instead provide a mutable input, which is ok since we are only reading
486+
// from it.
487+
char* mutable_bytes = const_cast<char*>(bytes);
488+
for (size_t i = 0; i < length; i++) {
489+
uint8_t byte =
490+
std::atomic_ref<char>(mutable_bytes[i]).load(std::memory_order_relaxed);
491+
PerformNibbleToHexAndWriteIntoStringOutPut(byte, index, string_output);
492+
index += 2;
471493
}
472494
}
473495

@@ -596,11 +618,14 @@ void Uint8ArrayToHexFastWithNeon(const char* bytes, uint8_t* output,
596618
#endif
597619
} // namespace
598620

599-
Tagged<Object> Uint8ArrayToHex(const char* bytes, size_t length,
621+
Tagged<Object> Uint8ArrayToHex(const char* bytes, size_t length, bool is_shared,
600622
DirectHandle<SeqOneByteString> string_output) {
623+
// TODO(rezvan): Add relaxed version for simd methods to handle shared array
624+
// buffers.
625+
601626
#ifdef __SSE3__
602-
if (get_vectorization_kind() == SimdKinds::kAVX2 ||
603-
get_vectorization_kind() == SimdKinds::kSSE) {
627+
if (!is_shared && (get_vectorization_kind() == SimdKinds::kAVX2 ||
628+
get_vectorization_kind() == SimdKinds::kSSE)) {
604629
{
605630
DisallowGarbageCollection no_gc;
606631
Uint8ArrayToHexFastWithSSE(bytes, string_output->GetChars(no_gc), length);
@@ -610,7 +635,7 @@ Tagged<Object> Uint8ArrayToHex(const char* bytes, size_t length,
610635
#endif
611636

612637
#ifdef NEON64
613-
if (get_vectorization_kind() == SimdKinds::kNeon) {
638+
if (!is_shared && get_vectorization_kind() == SimdKinds::kNeon) {
614639
{
615640
DisallowGarbageCollection no_gc;
616641
Uint8ArrayToHexFastWithNeon(bytes, string_output->GetChars(no_gc),
@@ -620,7 +645,11 @@ Tagged<Object> Uint8ArrayToHex(const char* bytes, size_t length,
620645
}
621646
#endif
622647

623-
Uint8ArrayToHexSlow(bytes, length, string_output);
648+
if (is_shared) {
649+
AtomicUint8ArrayToHexSlow(bytes, length, string_output);
650+
} else {
651+
Uint8ArrayToHexSlow(bytes, length, string_output);
652+
}
624653
return *string_output;
625654
}
626655

@@ -1026,20 +1055,23 @@ bool Uint8ArrayFromHexWithNeon(const base::Vector<T>& input_vector,
10261055
} // namespace
10271056

10281057
template <typename T>
1029-
bool ArrayBufferFromHex(const base::Vector<T>& input_vector, uint8_t* buffer,
1030-
size_t output_length) {
1058+
bool ArrayBufferFromHex(const base::Vector<T>& input_vector, bool is_shared,
1059+
uint8_t* buffer, size_t output_length) {
10311060
size_t input_length = input_vector.size();
10321061
DCHECK_LE(output_length, input_length / 2);
10331062

1063+
// TODO(rezvan): Add relaxed version for simd methods to handle shared array
1064+
// buffers.
1065+
10341066
#ifdef __SSE3__
1035-
if (get_vectorization_kind() == SimdKinds::kAVX2 ||
1036-
get_vectorization_kind() == SimdKinds::kSSE) {
1067+
if (!is_shared && (get_vectorization_kind() == SimdKinds::kAVX2 ||
1068+
get_vectorization_kind() == SimdKinds::kSSE)) {
10371069
return Uint8ArrayFromHexWithSSE(input_vector, buffer, output_length);
10381070
}
10391071
#endif
10401072

10411073
#ifdef NEON64
1042-
if (get_vectorization_kind() == SimdKinds::kNeon) {
1074+
if (!is_shared && get_vectorization_kind() == SimdKinds::kNeon) {
10431075
return Uint8ArrayFromHexWithNeon(input_vector, buffer, output_length);
10441076
}
10451077
#endif
@@ -1049,7 +1081,12 @@ bool ArrayBufferFromHex(const base::Vector<T>& input_vector, uint8_t* buffer,
10491081
for (uint32_t i = 0; i < input_length; i += 2) {
10501082
result = HandleRemainingHexValues(input_vector, i);
10511083
if (result.has_value()) {
1052-
buffer[index++] = result.value();
1084+
if (is_shared) {
1085+
std::atomic_ref<uint8_t>(buffer[index++])
1086+
.store(result.value(), std::memory_order_relaxed);
1087+
} else {
1088+
buffer[index++] = result.value();
1089+
}
10531090
} else {
10541091
return false;
10551092
}
@@ -1058,11 +1095,11 @@ bool ArrayBufferFromHex(const base::Vector<T>& input_vector, uint8_t* buffer,
10581095
}
10591096

10601097
template bool ArrayBufferFromHex(
1061-
const base::Vector<const uint8_t>& input_vector, uint8_t* buffer,
1062-
size_t output_length);
1098+
const base::Vector<const uint8_t>& input_vector, bool is_shared,
1099+
uint8_t* buffer, size_t output_length);
10631100
template bool ArrayBufferFromHex(
1064-
const base::Vector<const base::uc16>& input_vector, uint8_t* buffer,
1065-
size_t output_length);
1101+
const base::Vector<const base::uc16>& input_vector, bool is_shared,
1102+
uint8_t* buffer, size_t output_length);
10661103

10671104
#ifdef NEON64
10681105
#undef NEON64

src/objects/simd.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ uintptr_t ArrayIndexOfIncludesSmiOrObject(Address array_start,
2020
uintptr_t ArrayIndexOfIncludesDouble(Address array_start, uintptr_t array_len,
2121
uintptr_t from_index,
2222
Address search_element);
23-
Tagged<Object> Uint8ArrayToHex(const char* bytes, size_t length,
23+
Tagged<Object> Uint8ArrayToHex(const char* bytes, size_t length, bool is_shared,
2424
DirectHandle<SeqOneByteString> string_output);
2525
template <typename T>
26-
bool ArrayBufferFromHex(const base::Vector<T>& input_vector, uint8_t* buffer,
27-
size_t output_length);
26+
bool ArrayBufferFromHex(const base::Vector<T>& input_vector, bool is_shared,
27+
uint8_t* buffer, size_t output_length);
2828

2929
} // namespace internal
3030
} // namespace v8
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright 2025 the V8 project authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// Flags: --js-base-64 --allow-natives-syntax
6+
7+
const workerScript = `
8+
onmessage = function(event) {
9+
const sab = event.data.buffer;
10+
const uint8Array = new Uint8Array(sab);
11+
12+
const dataToWrite = [102, 111, 111, 98, 97, 114, 255, 255];
13+
14+
for (let i = 0; i < dataToWrite.length; ++i) {
15+
uint8Array[i] = dataToWrite[i];
16+
}
17+
18+
postMessage("started");
19+
20+
while (true) {
21+
for (let i = 0; i < dataToWrite.length; ++i) {
22+
uint8Array[i] = dataToWrite[i];
23+
}
24+
}
25+
};
26+
`;
27+
28+
function testConcurrentSharedArrayBufferUint8ArraySetFromHex() {
29+
const sab = new SharedArrayBuffer(8);
30+
const uint8ArrayMain = new Uint8Array(sab);
31+
32+
// Create a worker
33+
const worker = new Worker(workerScript, {type: 'string'});
34+
35+
// Send the SharedArrayBuffer
36+
worker.postMessage({buffer: sab});
37+
assertEquals('started', worker.getMessage());
38+
39+
// Give the worker a little time to write
40+
for (let i = 0; i < 10000; ++i) {
41+
}
42+
43+
// Call setFromHex on the main thread's view of the SAB
44+
for (let i = 0; i < 100; i++) {
45+
const result = uint8ArrayMain.setFromHex('666f6f626172');
46+
47+
const actual = Array.from(uint8ArrayMain);
48+
49+
assertEquals(
50+
actual, [102, 111, 111, 98, 97, 114, 255, 255],
51+
'setFromHex result mismatch with concurrent writes');
52+
}
53+
54+
// Terminate the worker (now it should exit its loop)
55+
worker.terminate();
56+
}
57+
58+
// Run the test function
59+
testConcurrentSharedArrayBufferUint8ArraySetFromHex();
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2025 the V8 project authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// Flags: --js-base-64 --allow-natives-syntax
6+
7+
const workerScript = `
8+
onmessage = function(event) {
9+
const sab = event.data.buffer;
10+
const uint8Array = new Uint8Array(sab);
11+
12+
const dataToWrite = [102, 111, 111, 98, 97, 114];
13+
14+
for (let i = 0; i < dataToWrite.length; ++i) {
15+
uint8Array[i] = dataToWrite[i];
16+
}
17+
18+
postMessage("started");
19+
20+
while (true) {
21+
for (let i = 0; i < dataToWrite.length; ++i) {
22+
uint8Array[i] = dataToWrite[i];
23+
}
24+
}
25+
};
26+
`;
27+
28+
function testConcurrentSharedArrayBufferUint8ArrayToHex() {
29+
const sab = new SharedArrayBuffer(6);
30+
const uint8ArrayMain = new Uint8Array(sab);
31+
32+
// Create a worker
33+
const worker = new Worker(workerScript, {type: 'string'});
34+
35+
// Send the SharedArrayBuffer
36+
worker.postMessage({buffer: sab});
37+
assertEquals('started', worker.getMessage());
38+
39+
// Give the worker a little time to write
40+
for (let i = 0; i < 10000; ++i) {
41+
}
42+
43+
// Call toHex on the main thread's view of the SAB
44+
for (let i = 0; i < 100; i++) {
45+
const hexString = uint8ArrayMain.toHex();
46+
assertEquals(
47+
'666f6f626172', hexString,
48+
'toHex result mismatch with concurrent writes');
49+
}
50+
51+
// Terminate the worker (now it should exit its loop)
52+
worker.terminate();
53+
}
54+
55+
// Run the test function
56+
testConcurrentSharedArrayBufferUint8ArrayToHex();

0 commit comments

Comments
 (0)