v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
simd.cc
Go to the documentation of this file.
1// Copyright 2022 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#include "src/objects/simd.h"
6
7#include "src/base/cpu.h"
13#include "src/objects/smi-inl.h"
14
15#ifdef _MSC_VER
16// MSVC doesn't define SSE3. However, it does define AVX, and AVX implies SSE3.
17#ifdef __AVX__
18#ifndef __SSE3__
19#define __SSE3__
20#endif
21#endif
22#endif
23
24#ifdef __SSE3__
25#include <immintrin.h>
26#endif
27
28#ifdef V8_HOST_ARCH_ARM64
29// We use Neon only on 64-bit ARM (because on 32-bit, some instructions and some
30// types are not available). Note that ARM64 is guaranteed to have Neon.
31#define NEON64
32#include <arm_neon.h>
33#endif
34
35namespace v8 {
36namespace internal {
37
38namespace {
39
40enum class SimdKinds { kSSE, kNeon, kAVX2, kNone };
41
42inline SimdKinds get_vectorization_kind() {
43#ifdef __SSE3__
44#if defined(V8_TARGET_ARCH_IA32) || defined(V8_TARGET_ARCH_X64)
45 bool has_avx2 = CpuFeatures::IsSupported(AVX2);
46#else
47 bool has_avx2 = false;
48#endif
49 if (has_avx2) {
50 return SimdKinds::kAVX2;
51 } else {
52 // No need for a runtime check since we do not support x86/x64 CPUs without
53 // SSE3.
54 return SimdKinds::kSSE;
55 }
56#elif defined(NEON64)
57 // No need for a runtime check since all Arm64 CPUs have Neon.
58 return SimdKinds::kNeon;
59#else
60 return SimdKinds::kNone;
61#endif
62}
63
64// Searches for |search_element| in |array| using a simple non-vectorized linear
65// search. This is used as a fall-back when SIMD are not available, and to
66// process the end of arrays than SIMD cannot process.
67template <typename T>
68inline uintptr_t slow_search(T* array, uintptr_t array_len, uintptr_t index,
69 T search_element) {
70 for (; index < array_len; index++) {
71 if (array[index] == search_element) {
72 return index;
73 }
74 }
75 return -1;
76}
77
78#ifdef NEON64
79// extract_first_nonzero_index returns the first non-zero index in |v|. |v| is a
80// Neon vector that can be either 32x4 (the return is then 0, 1, 2 or 3) or 64x2
81// (the return is then 0 or 1). This is more or less equivalent to doing a
82// movemask followed by a tzcnt on Intel.
83//
84// The input |v| should be a vector of -1 or 0 (for instance {0, 0},
85// {0, -1, 0, -1}, {0, -1, 0, 0}), where -1 represents a match (and 0 a
86// non-match), that was obtained by doing a vceqq. This function extract the
87// index of the first non-zero item of the vector. To do so, we "and" the vector
88// with {4, 3, 2, 1} (each number is "4 - the index of the item it's in"), which
89// produces a vector of "indices or 0". Then, we extract the maximum of this
90// vector, which is the index of the 1st match. An example:
91//
92// v = {-1, 0, 0, -1}
93// mask = {4, 3, 2, 1}
94// v & mask = {4, 0, 0, 1}
95// max(v & mask) = 4
96// index of the first match = 4-max = 4-4 = 0
97//
98
99// With MSVC, uint32x4_t and uint64x2_t typedef to a union, where first member
100// is uint64_t[2], and not uint32_t[4].
101// C++ standard dictates that a union can only be initialized through its first
102// member, which forces us to have uint64_t[2] for definition.
103#if defined(_MSC_VER) && !defined(__clang__)
104#define PACK32x4(w, x, y, z) \
105 { ((w) + (uint64_t(x) << 32)), ((y) + (uint64_t(z) << 32)) }
106#else
107#define PACK32x4(w, x, y, z) \
108 { (w), (x), (y), (z) }
109#endif // MSVC workaround
110
111V8_ALLOW_UNUSED inline int extract_first_nonzero_index_uint32x4_t(
112 uint32x4_t v) {
113 uint32x4_t mask = PACK32x4(4, 3, 2, 1);
114 mask = vandq_u32(mask, v);
115 return 4 - vmaxvq_u32(mask);
116}
117
118inline int extract_first_nonzero_index_uint64x2_t(uint64x2_t v) {
119 uint32x4_t mask =
120 PACK32x4(2, 0, 1, 0); // Could also be {2,2,1,1} or {0,2,0,1}
121 mask = vandq_u32(mask, vreinterpretq_u32_u64(v));
122 return 2 - vmaxvq_u32(mask);
123}
124
125inline int32_t reinterpret_vmaxvq_u64(uint64x2_t v) {
126 return vmaxvq_u32(vreinterpretq_u32_u64(v));
127}
128#endif
129
130#define VECTORIZED_LOOP_Neon(type_load, type_eq, set1, cmp, movemask) \
131 { \
132 constexpr int elems_in_vector = sizeof(type_load) / sizeof(T); \
133 type_load search_element_vec = set1(search_element); \
134 \
135 for (; index + elems_in_vector <= array_len; index += elems_in_vector) { \
136 type_load vector = *reinterpret_cast<type_load*>(&array[index]); \
137 type_eq eq = cmp(vector, search_element_vec); \
138 if (movemask(eq)) { \
139 return index + extract_first_nonzero_index_##type_eq(eq); \
140 } \
141 } \
142 }
143
144#define VECTORIZED_LOOP_x86(type_load, type_eq, set1, cmp, movemask, extract) \
145 { \
146 constexpr int elems_in_vector = sizeof(type_load) / sizeof(T); \
147 type_load search_element_vec = set1(search_element); \
148 \
149 for (; index + elems_in_vector <= array_len; index += elems_in_vector) { \
150 type_load vector = *reinterpret_cast<type_load*>(&array[index]); \
151 type_eq eq = cmp(vector, search_element_vec); \
152 int eq_mask = movemask(eq); \
153 if (eq_mask) { \
154 return index + extract(eq_mask); \
155 } \
156 } \
157 }
158
159#ifdef __SSE3__
160__m128i _mm_cmpeq_epi64_nosse4_2(__m128i a, __m128i b) {
161 __m128i res = _mm_cmpeq_epi32(a, b);
162 // For each 64-bit value swap results of lower 32 bits comparison with
163 // the results of upper 32 bits comparison.
164 __m128i res_swapped = _mm_shuffle_epi32(res, _MM_SHUFFLE(2, 3, 0, 1));
165 // Report match only when both upper and lower parts of 64-bit values match.
166 return _mm_and_si128(res, res_swapped);
167}
168#endif // __SSE3__
169
170// Uses SIMD to vectorize the search loop. This function should only be called
171// for large-ish arrays. Note that nothing will break if |array_len| is less
172// than vectorization_threshold: things will just be slower than necessary.
173template <typename T>
174inline uintptr_t fast_search_noavx(T* array, uintptr_t array_len,
175 uintptr_t index, T search_element) {
176 static constexpr bool is_uint32 =
177 sizeof(T) == sizeof(uint32_t) && std::is_integral_v<T>;
178 static constexpr bool is_uint64 =
179 sizeof(T) == sizeof(uint64_t) && std::is_integral_v<T>;
180 static constexpr bool is_double =
181 sizeof(T) == sizeof(double) && std::is_floating_point_v<T>;
182
183 static_assert(is_uint32 || is_uint64 || is_double);
184
185#if !(defined(__SSE3__) || defined(NEON64))
186 // No SIMD available.
187 return slow_search(array, array_len, index, search_element);
188#endif
189
190#ifdef __SSE3__
191 const int target_align = 16;
192#elif defined(NEON64)
193 const int target_align = 16;
194#else
195 const int target_align = 4;
196 UNREACHABLE();
197#endif
198
199 // Scalar loop to reach desired alignment
200 for (;
201 index < array_len &&
202 (reinterpret_cast<std::uintptr_t>(&(array[index])) % target_align) != 0;
203 index++) {
204 if (array[index] == search_element) {
205 return index;
206 }
207 }
208
209 // Inserting one of the vectorized loop
210#ifdef __SSE3__
211 if constexpr (is_uint32) {
212#define MOVEMASK(x) _mm_movemask_ps(_mm_castsi128_ps(x))
213#define EXTRACT(x) base::bits::CountTrailingZeros32(x)
214 VECTORIZED_LOOP_x86(__m128i, __m128i, _mm_set1_epi32, _mm_cmpeq_epi32,
215 MOVEMASK, EXTRACT)
216#undef MOVEMASK
217#undef EXTRACT
218 } else if constexpr (is_uint64) {
219#define MOVEMASK(x) _mm_movemask_ps(_mm_castsi128_ps(x))
220// _mm_cmpeq_epi64_nosse4_2() might produce only the following non-zero
221// patterns:
222// 0b0011 -> 0 (the first value matches),
223// 0b1100 -> 1 (the second value matches),
224// 0b1111 -> 0 (both first and second value match).
225// Thus it's enough to check only the least significant bit.
226#define EXTRACT(x) (((x) & 1) ? 0 : 1)
227 VECTORIZED_LOOP_x86(__m128i, __m128i, _mm_set1_epi64x,
228 _mm_cmpeq_epi64_nosse4_2, MOVEMASK, EXTRACT)
229#undef MOVEMASK
230#undef EXTRACT
231 } else if constexpr (is_double) {
232#define EXTRACT(x) base::bits::CountTrailingZeros32(x)
233 VECTORIZED_LOOP_x86(__m128d, __m128d, _mm_set1_pd, _mm_cmpeq_pd,
234 _mm_movemask_pd, EXTRACT)
235#undef EXTRACT
236 }
237#elif defined(NEON64)
238 if constexpr (is_uint32) {
239 VECTORIZED_LOOP_Neon(uint32x4_t, uint32x4_t, vdupq_n_u32, vceqq_u32,
240 vmaxvq_u32)
241 } else if constexpr (is_uint64) {
242 VECTORIZED_LOOP_Neon(uint64x2_t, uint64x2_t, vdupq_n_u64, vceqq_u64,
243 reinterpret_vmaxvq_u64)
244 } else if constexpr (is_double) {
245 VECTORIZED_LOOP_Neon(float64x2_t, uint64x2_t, vdupq_n_f64, vceqq_f64,
246 reinterpret_vmaxvq_u64)
247 }
248#else
249 UNREACHABLE();
250#endif
251
252 // The vectorized loop stops when there are not enough items left in the array
253 // to fill a vector register. The slow_search function will take care of
254 // iterating through the few remaining items.
255 return slow_search(array, array_len, index, search_element);
256}
257
258#if defined(_MSC_VER) && defined(__clang__)
259// Generating AVX2 code with Clang on Windows without the /arch:AVX2 flag does
260// not seem possible at the moment.
261#define IS_CLANG_WIN 1
262#endif
263
264// Since we don't compile with -mavx or -mavx2 (or /arch:AVX2 on MSVC), Clang
265// and MSVC do not define __AVX__ nor __AVX2__. Thus, if __SSE3__ is defined, we
266// generate the AVX2 code, and, at runtime, we'll decide to call it or not,
267// depending on whether the CPU supports AVX2.
268#if defined(__SSE3__) && !defined(_M_IX86) && !defined(IS_CLANG_WIN)
269#ifdef _MSC_VER
270#define TARGET_AVX2
271#else
272#define TARGET_AVX2 __attribute__((target("avx2")))
273#endif
274template <typename T>
275TARGET_AVX2 inline uintptr_t fast_search_avx(T* array, uintptr_t array_len,
276 uintptr_t index,
277 T search_element) {
278 static constexpr bool is_uint32 =
279 sizeof(T) == sizeof(uint32_t) && std::is_integral_v<T>;
280 static constexpr bool is_uint64 =
281 sizeof(T) == sizeof(uint64_t) && std::is_integral_v<T>;
282 static constexpr bool is_double =
283 sizeof(T) == sizeof(double) && std::is_floating_point_v<T>;
284
285 static_assert(is_uint32 || is_uint64 || is_double);
286
287 const int target_align = 32;
288 // Scalar loop to reach desired alignment
289 for (;
290 index < array_len &&
291 (reinterpret_cast<std::uintptr_t>(&(array[index])) % target_align) != 0;
292 index++) {
293 if (array[index] == search_element) {
294 return index;
295 }
296 }
297
298 // Generating vectorized loop
299 if constexpr (is_uint32) {
300#define MOVEMASK(x) _mm256_movemask_ps(_mm256_castsi256_ps(x))
301#define EXTRACT(x) base::bits::CountTrailingZeros32(x)
302 VECTORIZED_LOOP_x86(__m256i, __m256i, _mm256_set1_epi32, _mm256_cmpeq_epi32,
303 MOVEMASK, EXTRACT)
304#undef MOVEMASK
305#undef EXTRACT
306 } else if constexpr (is_uint64) {
307#define MOVEMASK(x) _mm256_movemask_pd(_mm256_castsi256_pd(x))
308#define EXTRACT(x) base::bits::CountTrailingZeros32(x)
309 VECTORIZED_LOOP_x86(__m256i, __m256i, _mm256_set1_epi64x,
310 _mm256_cmpeq_epi64, MOVEMASK, EXTRACT)
311#undef MOVEMASK
312#undef EXTRACT
313 } else if constexpr (is_double) {
314#define CMP(a, b) _mm256_cmp_pd(a, b, _CMP_EQ_OQ)
315#define EXTRACT(x) base::bits::CountTrailingZeros32(x)
316 VECTORIZED_LOOP_x86(__m256d, __m256d, _mm256_set1_pd, CMP,
317 _mm256_movemask_pd, EXTRACT)
318#undef CMP
319#undef EXTRACT
320 }
321
322 // The vectorized loop stops when there are not enough items left in the array
323 // to fill a vector register. The slow_search function will take care of
324 // iterating through the few remaining items.
325 return slow_search(array, array_len, index, search_element);
326}
327
328#undef TARGET_AVX2
329#elif defined(IS_CLANG_WIN)
330template <typename T>
331inline uintptr_t fast_search_avx(T* array, uintptr_t array_len, uintptr_t index,
332 T search_element) {
333 // Falling back to SSE version
334 return fast_search_noavx(array, array_len, index, search_element);
335}
336#else
337template <typename T>
338uintptr_t fast_search_avx(T* array, uintptr_t array_len, uintptr_t index,
339 T search_element) {
340 UNREACHABLE();
341}
342#endif // ifdef __SSE3__
343
344#undef IS_CLANG_WIN
345#undef VECTORIZED_LOOP_Neon
346#undef VECTORIZED_LOOP_x86
347
348template <typename T>
349inline uintptr_t search(T* array, uintptr_t array_len, uintptr_t index,
350 T search_element) {
351 if (get_vectorization_kind() == SimdKinds::kAVX2) {
352 return fast_search_avx(array, array_len, index, search_element);
353 } else {
354 return fast_search_noavx(array, array_len, index, search_element);
355 }
356}
357
358enum class ArrayIndexOfIncludesKind { DOUBLE, OBJECTORSMI };
359
360// ArrayIndexOfIncludes only handles cases that can be efficiently
361// vectorized:
362//
363// * Searching for a Smi in a Smi array
364//
365// * Searching for a Smi or Double in a Double array
366//
367// * Searching for an object in an object array.
368//
369// Other cases should be dealt with either with the CSA builtin or with the
370// inlined optimized code.
371template <ArrayIndexOfIncludesKind kind>
372Address ArrayIndexOfIncludes(Address array_start, uintptr_t array_len,
373 uintptr_t from_index, Address search_element) {
374 if (array_len == 0) {
375 return Smi::FromInt(-1).ptr();
376 }
377
378 if constexpr (kind == ArrayIndexOfIncludesKind::DOUBLE) {
379 Tagged<FixedDoubleArray> fixed_array =
381 UnalignedDoubleMember* unaligned_array = fixed_array->begin();
382 // TODO(leszeks): This reinterpret cast is a bit sketchy because the values
383 // are unaligned doubles. Ideally we'd fix the search method to support
384 // UnalignedDoubleMember.
385 static_assert(sizeof(UnalignedDoubleMember) == sizeof(double));
386 double* array = reinterpret_cast<double*>(unaligned_array);
387
388 double search_num;
389 if (IsSmi(Tagged<Object>(search_element))) {
390 search_num = Tagged<Object>(search_element).ToSmi().value();
391 } else {
392 DCHECK(IsHeapNumber(Tagged<Object>(search_element)));
393 search_num = Cast<HeapNumber>(Tagged<Object>(search_element))->value();
394 }
395
396 DCHECK(!std::isnan(search_num));
397
398 if (reinterpret_cast<uintptr_t>(array) % sizeof(double) != 0) {
399 // Slow scalar search for unaligned double array.
400 for (; from_index < array_len; from_index++) {
401 if (fixed_array->is_the_hole(static_cast<int>(from_index))) {
402 // |search_num| cannot be NaN, so there is no need to check against
403 // holes.
404 continue;
405 }
406 if (fixed_array->get_scalar(static_cast<int>(from_index)) ==
407 search_num) {
408 return from_index;
409 }
410 }
411 return Smi::FromInt(-1).ptr();
412 }
413
414 return search<double>(array, array_len, from_index, search_num);
415 }
416
417 if constexpr (kind == ArrayIndexOfIncludesKind::OBJECTORSMI) {
418 Tagged<FixedArray> fixed_array =
419 Cast<FixedArray>(Tagged<Object>(array_start));
420 Tagged_t* array = static_cast<Tagged_t*>(
421 fixed_array->RawFieldOfFirstElement().ToVoidPtr());
422
423 DCHECK(!IsHeapNumber(Tagged<Object>(search_element)));
424 DCHECK(!IsBigInt(Tagged<Object>(search_element)));
425 DCHECK(!IsString(Tagged<Object>(search_element)));
426
427 return search<Tagged_t>(array, array_len, from_index,
428 static_cast<Tagged_t>(search_element));
429 }
430}
431
432} // namespace
433
435 uintptr_t array_len,
436 uintptr_t from_index,
437 Address search_element) {
438 return ArrayIndexOfIncludes<ArrayIndexOfIncludesKind::OBJECTORSMI>(
439 array_start, array_len, from_index, search_element);
440}
441
442uintptr_t ArrayIndexOfIncludesDouble(Address array_start, uintptr_t array_len,
443 uintptr_t from_index,
444 Address search_element) {
445 return ArrayIndexOfIncludes<ArrayIndexOfIncludesKind::DOUBLE>(
446 array_start, array_len, from_index, search_element);
447}
448
449// http://0x80.pl/notesen/2014-09-21-convert-to-hex.html
450namespace {
451
452char NibbleToHex(uint8_t nibble) {
453 const char correction = 'a' - '0' - 10;
454 const char c = nibble + '0';
455 uint8_t temp = 128 - 10 + nibble;
456 uint8_t msb = temp & 0x80;
457 uint8_t mask = msb - (msb >> 7);
458 return c + (mask & correction);
459}
460
461void Uint8ArrayToHexSlow(const char* bytes, size_t length,
462 DirectHandle<SeqOneByteString> string_output) {
463 int index = 0;
464 for (size_t i = 0; i < length; i++) {
465 uint8_t byte = bytes[i];
466 uint8_t high = byte >> 4;
467 uint8_t low = byte & 0x0F;
468
469 string_output->SeqOneByteStringSet(index++, NibbleToHex(high));
470 string_output->SeqOneByteStringSet(index++, NibbleToHex(low));
471 }
472}
473
474inline uint16_t ByteToHex(uint8_t byte) {
475 const uint16_t correction = (('a' - '0' - 10) << 8) + ('a' - '0' - 10);
476#if V8_TARGET_BIG_ENDIAN
477 const uint16_t nibbles = (byte << 4) + (byte & 0xF);
478#else
479 const uint16_t nibbles = ((byte & 0xF) << 8) + (byte >> 4);
480#endif
481 const uint16_t chars = nibbles + 0x3030;
482 const uint16_t temp = 0x8080 - 0x0A0A + nibbles;
483 const uint16_t msb = temp & 0x8080;
484 const uint16_t mask = msb - (msb >> 7);
485 return chars + (mask & correction);
486}
487
488V8_ALLOW_UNUSED void HandleRemainingNibbles(const char* bytes, uint8_t* output,
489 size_t length, size_t i) {
490 uint16_t* output_pairs = reinterpret_cast<uint16_t*>(output) + i;
491 bytes += i;
492 size_t rest = length & 0x7;
493 for (i = 0; i < rest; i++) {
494 *(output_pairs++) = ByteToHex(*bytes++);
495 }
496}
497
522#ifdef __SSE3__
523void Uint8ArrayToHexFastWithSSE(const char* bytes, uint8_t* output,
524 size_t length) {
525 size_t i;
526 size_t index = 0;
527 alignas(16) uint8_t nibbles_buffer[16];
528 for (i = 0; i + 8 <= length; i += 8) {
529 index = 0;
530 for (size_t j = i; j < i + 8; j++) {
531 nibbles_buffer[index++] = bytes[j] >> 4; // High nibble
532 nibbles_buffer[index++] = bytes[j] & 0x0F; // Low nibble
533 }
534
535 // Load data into SSE registers
536 __m128i nibbles =
537 _mm_load_si128(reinterpret_cast<__m128i*>(nibbles_buffer));
538 __m128i nine = _mm_set1_epi8(9);
539 __m128i ascii_0 = _mm_set1_epi8('0');
540 __m128i correction = _mm_set1_epi8('a' - 10 - '0');
541
542 // Make a copy for ASCII conversion
543 __m128i ascii_result = _mm_add_epi8(nibbles, ascii_0);
544
545 // Create a mask for values greater than 9
546 __m128i mask = _mm_cmpgt_epi8(nibbles, nine);
547
548 // Apply correction
549 __m128i corrected_result = _mm_and_si128(mask, correction);
550 corrected_result = _mm_add_epi8(ascii_result, corrected_result);
551
552 // Store the result
553 _mm_storeu_si128(reinterpret_cast<__m128i*>(&output[i * 2]),
554 corrected_result);
555 }
556
557 HandleRemainingNibbles(bytes, output, length, i);
558}
559#endif
560
561#ifdef NEON64
562void Uint8ArrayToHexFastWithNeon(const char* bytes, uint8_t* output,
563 size_t length) {
564 size_t i;
565 size_t index = 0;
566 alignas(16) uint8_t nibbles_buffer[16];
567 for (i = 0; i + 8 <= length; i += 8) {
568 index = 0;
569 for (size_t j = i; j < i + 8; j++) {
570 nibbles_buffer[index++] = bytes[j] >> 4; // High nibble
571 nibbles_buffer[index++] = bytes[j] & 0x0F; // Low nibble
572 }
573
574 // Load data into NEON registers
575 uint8x16_t nibbles = vld1q_u8(nibbles_buffer);
576 uint8x16_t nine = vdupq_n_u8(9);
577 uint8x16_t ascii0 = vdupq_n_u8('0');
578 uint8x16_t correction = vdupq_n_u8('a' - 10 - '0');
579
580 // Make a copy for ASCII conversion
581 uint8x16_t ascii_result = vaddq_u8(nibbles, ascii0);
582
583 // Create a mask for values greater than 9
584 uint8x16_t mask = vcgtq_u8(nibbles, nine);
585
586 // Apply correction
587 uint8x16_t corrected_result = vandq_u8(mask, correction);
588 corrected_result = vaddq_u8(ascii_result, corrected_result);
589
590 // Store the result
591 vst1q_u8(&output[i * 2], corrected_result);
592 }
593
594 HandleRemainingNibbles(bytes, output, length, i);
595}
596#endif
597} // namespace
598
599Tagged<Object> Uint8ArrayToHex(const char* bytes, size_t length,
600 DirectHandle<SeqOneByteString> string_output) {
601#ifdef __SSE3__
602 if (get_vectorization_kind() == SimdKinds::kAVX2 ||
603 get_vectorization_kind() == SimdKinds::kSSE) {
604 {
606 Uint8ArrayToHexFastWithSSE(bytes, string_output->GetChars(no_gc), length);
607 }
608 return *string_output;
609 }
610#endif
611
612#ifdef NEON64
613 if (get_vectorization_kind() == SimdKinds::kNeon) {
614 {
616 Uint8ArrayToHexFastWithNeon(bytes, string_output->GetChars(no_gc),
617 length);
618 }
619 return *string_output;
620 }
621#endif
622
623 Uint8ArrayToHexSlow(bytes, length, string_output);
624 return *string_output;
625}
626
627namespace {
628
629Maybe<uint8_t> HexToUint8(base::uc16 hex) {
630 if (hex >= '0' && hex <= '9') {
631 return Just<uint8_t>(hex - '0');
632 } else if (hex >= 'a' && hex <= 'f') {
633 return Just<uint8_t>(hex - 'a' + 10);
634 } else if (hex >= 'A' && hex <= 'F') {
635 return Just<uint8_t>(hex - 'A' + 10);
636 }
637
638 return Nothing<uint8_t>();
639}
640
641template <typename T>
642std::optional<uint8_t> HandleRemainingHexValues(base::Vector<T>& input_vector,
643 size_t i) {
644 T higher = input_vector[i];
645 T lower = input_vector[i + 1];
646
647 uint8_t result_high = 0;
648 Maybe<uint8_t> maybe_result_high = HexToUint8(higher);
649 if (!maybe_result_high.To(&result_high)) {
650 return {};
651 }
652
653 uint8_t result_low = 0;
654 Maybe<uint8_t> maybe_result_low = HexToUint8(lower);
655 if (!maybe_result_low.To(&result_low)) {
656 return {};
657 }
658
659 result_high <<= 4;
660 uint8_t result = result_high + result_low;
661 return result;
662}
663
664#ifdef __SSE3__
665const __m128i char_0 = _mm_set1_epi8('0');
666
667inline std::optional<__m128i> HexToUint8FastWithSSE(__m128i nibbles) {
668 // Example:
669 // nibbles: {0x36, 0x66, 0x66, 0x32, 0x31, 0x32, 0x31, 0x32, 0x36, 0x66, 0x66,
670 // 0x32, 0x31, 0x32, 0x31, 0x66}
671
672 static const __m128i char_a = _mm_set1_epi8('a');
673 static const __m128i char_A = _mm_set1_epi8('A');
674 static const __m128i all_10 = _mm_set1_epi8(10);
675 static const __m128i all_6 = _mm_set1_epi8(6);
676
677 // Create masks and nibbles for different character ranges
678 // Valid hexadecimal values are 0-9, a-f and A-F.
679 // mask_09 is 0xff when the corresponding value in nibbles is in range
680 // of 0 to 9. nibbles_09 is value-'0' and 0x0 for the rest of the values.
681 // Similar description apply to mask_af, mask_AF, nibbles_af and nibbles_af.
682
683 // mask_09: {0xff, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0,
684 // 0xff, 0xff, 0xff, 0xff, 0x0}
685 // nibbles_09: {0x6, 0x0, 0x0, 0x2, 0x1, 0x2, 0x1, 0x2, 0x6, 0x0,
686 // 0x0, 0x2, 0x1, 0x2, 0x1, 0x0}
687 __m128i nibbles_09 = _mm_sub_epi8(nibbles, char_0);
688 // If the value is in the expected range (for 09 set is between 0-9), then it
689 // will be less than specified max (in this case 10) and the result for this
690 // corresponding value is 0xff. For the rest of the values, it will never be
691 // less than itself (max in that case) and the result is 0x0.
692 __m128i mask_09 =
693 _mm_cmplt_epi8(nibbles_09, _mm_max_epu8(nibbles_09, all_10));
694 nibbles_09 = _mm_and_si128(nibbles_09, mask_09);
695
696 // mask_af: {0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0,
697 // 0x0, 0x0, 0x0, 0xff}
698 // nibbles_af: {0x0, 0xf, 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xf, 0x0,
699 // 0x0, 0x0, 0x0, 0xf}
700 __m128i nibbles_af = _mm_sub_epi8(nibbles, char_a);
701 __m128i mask_af = _mm_cmplt_epi8(nibbles_af, _mm_max_epu8(nibbles_af, all_6));
702 nibbles_af = _mm_and_si128(_mm_add_epi8(nibbles_af, all_10), mask_af);
703
704 // mask_AF: {0x0 <repeats 16 times>}
705 __m128i nibbles_AF = _mm_sub_epi8(nibbles, char_A);
706 __m128i mask_AF = _mm_cmplt_epi8(nibbles_AF, _mm_max_epu8(nibbles_AF, all_6));
707 nibbles_AF = _mm_and_si128(_mm_add_epi8(nibbles_AF, all_10), mask_AF);
708
709 // Combine masks to check if all nibbles are valid hex values
710 // combined_mask: {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
711 // 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
712 __m128i combined_mask = _mm_or_si128(_mm_or_si128(mask_af, mask_AF), mask_09);
713
714 if (_mm_movemask_epi8(_mm_cmpeq_epi8(
715 combined_mask, _mm_set1_epi64x(0xffffffffffffffff))) != 0xFFFF) {
716 return {};
717 }
718
719 // Combine the results using bitwise OR
720 // returns {0x0, 0x6, 0x0, 0xf, 0x0, 0xf, 0x0, 0x2, 0x0, 0x1,
721 // 0x0, 0x2, 0x0, 0x1, 0x0, 0x2}
722 return _mm_or_si128(_mm_or_si128(nibbles_af, nibbles_AF), nibbles_09);
723}
724
725template <typename T>
726bool Uint8ArrayFromHexWithSSE(base::Vector<T>& input_vector,
727 DirectHandle<JSArrayBuffer> buffer,
728 size_t output_length) {
729 CHECK_EQ(buffer->GetByteLength(), output_length);
730 // Example:
731 // input_vector: 666f6f6261726172666f6f62617261ff
732 size_t i;
733
734 for (i = 0; i + 32 <= output_length * 2; i += 32) {
735 // Load first batch of 16 hex characters into an SSE register
736 // {0x36, 0x36, 0x36, 0x66, 0x36, 0x66, 0x36, 0x32, 0x36, 0x31,
737 // 0x37, 0x32, 0x36, 0x31, 0x37, 0x32}
738 __m128i first_batch =
739 _mm_loadu_si128(reinterpret_cast<const __m128i*>(&input_vector[i]));
740 // Handle TwoByteStrings
741 if constexpr (std::is_same_v<T, const base::uc16>) {
742 __m128i second_part_first_batch = _mm_loadu_si128(
743 reinterpret_cast<const __m128i*>(&input_vector[i + 8]));
744
745 first_batch = _mm_packus_epi16(first_batch, second_part_first_batch);
746 }
747
748 // Load second batch of 16 hex characters into an SSE register
749 // {0x36, 0x36, 0x36, 0x66, 0x36, 0x66, 0x36, 0x32, 0x36, 0x31, 0x37, 0x32,
750 // 0x36, 0x31, 0x66, 0x66}
751 __m128i second_batch = _mm_loadu_si128(
752 reinterpret_cast<const __m128i*>(&input_vector[i + 16]));
753 if constexpr (std::is_same_v<T, const base::uc16>) {
754 __m128i second_part_second_batch = _mm_loadu_si128(
755 reinterpret_cast<const __m128i*>(&input_vector[i + 24]));
756
757 second_batch = _mm_packus_epi16(second_batch, second_part_second_batch);
758 }
759
760 __m128i mask = _mm_set1_epi64((__m64)0x00ff00ff00ff00ff);
761
762 // low nibbles are values with even indexes in fist_batch.
763 // {0x36, 0x0, 0x66, 0x0, 0x66, 0x0, 0x32, 0x0, 0x31, 0x0,
764 // 0x32, 0x0, 0x31, 0x0, 0x32, 0x0}
765 __m128i first_batch_lo_nibbles = _mm_srli_epi16(first_batch, 8);
766
767 // high nibbles are values with odd indexes in first_batch.
768 // {0x36, 0x0, 0x36, 0x0, 0x36, 0x0, 0x36, 0x0, 0x36, 0x0,
769 // 0x37, 0x0, 0x36, 0x0, 0x37, 0x0}
770 __m128i first_batch_hi_nibbles = _mm_and_si128(first_batch, mask);
771
772 // low nibbles are values with even indexes in second_batch.
773 // {0x36, 0x0, 0x66, 0x0, 0x66, 0x0, 0x32, 0x0, 0x31, 0x0,
774 // 0x32, 0x0, 0x31, 0x0, 0x66, 0x0}
775 __m128i second_batch_lo_nibbles = _mm_srli_epi16(second_batch, 8);
776
777 // high nibbles are values with odd indexes in second_batch.
778 // {0x36, 0x0, 0x36, 0x0, 0x36, 0x0, 0x36, 0x0, 0x36, 0x0,
779 // 0x37, 0x0, 0x36, 0x0, 0x66, 0x0}
780 __m128i second_batch_hi_nibbles = _mm_and_si128(second_batch, mask);
781
782 // Append first_batch_lo_nibbles and second_batch_lo_nibbles and
783 // remove 0x0 values
784 // {0x36, 0x66, 0x66, 0x32, 0x31, 0x32, 0x31, 0x32, 0x36, 0x66, 0x66, 0x32,
785 // 0x31, 0x32, 0x31, 0x66}
786 __m128i lo_nibbles =
787 _mm_packus_epi16(first_batch_lo_nibbles, second_batch_lo_nibbles);
788
789 // Append first_batch_hi_nibbles and second_batch_hi_nibbles and
790 // remove 0x0 values
791 // {0x36, 0x36, 0x36, 0x36, 0x36, 0x37, 0x36, 0x37, 0x36, 0x36, 0x36, 0x36,
792 // 0x36, 0x37, 0x36, 0x66}
793 __m128i hi_nibbles =
794 _mm_packus_epi16(first_batch_hi_nibbles, second_batch_hi_nibbles);
795
796 // mapping low nibbles to uint8_t values.
797 // {0x6, 0xf, 0xf, 0x2, 0x1, 0x2, 0x1, 0x2, 0x6, 0xf, 0xf, 0x2, 0x1, 0x2,
798 // 0x1, 0xf}
799 std::optional<__m128i> maybe_uint8_low_nibbles =
800 HexToUint8FastWithSSE(lo_nibbles);
801
802 // Check if it is {} (includes invalid hex values)
803 if (!maybe_uint8_low_nibbles.has_value()) {
804 return false;
805 }
806 __m128i uint8_low_nibbles = maybe_uint8_low_nibbles.value();
807
808 // mapping high nibbles to uint8_t values.
809 // {0x6, 0x6, 0x6, 0x6, 0x6, 0x7, 0x6, 0x7, 0x6, 0x6, 0x6, 0x6, 0x6, 0x7,
810 // 0x6, 0xf}
811 std::optional<__m128i> maybe_uint8_high_nibbles =
812 HexToUint8FastWithSSE(hi_nibbles);
813
814 // Check if it is {} (includes invalid hex values)
815 if (!maybe_uint8_high_nibbles.has_value()) {
816 return false;
817 }
818 __m128i uint8_high_nibbles = maybe_uint8_high_nibbles.value();
819
820 // shift uint8_t values of high nibbles to be able to combine with low
821 // uint8_t values.
822 // {0x60, 0x60, 0x60, 0x60, 0x60, 0x70, 0x60, 0x70, 0x60, 0x60, 0x60, 0x60,
823 // 0x60, 0x70, 0x60, 0xf0}
824 __m128i uint8_shifted_high_nibbles = _mm_slli_epi64(uint8_high_nibbles, 4);
825
826 // final result of combining pairs of uint8_t values of low and high
827 // nibbles.
828 // {0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72, 0x61, 0x72, 0x66, 0x6f,
829 // 0x6f, 0x62, 0x61, 0x72, 0x61, 0xff}
830 __m128i final_result =
831 _mm_or_si128(uint8_shifted_high_nibbles, uint8_low_nibbles);
832
833 // store result in a buffer and it is equivalent to
834 // [102,111,111,98,97,114,97,114,102,111,111,98,97,114,97,255]
835 _mm_storeu_si128(reinterpret_cast<__m128i*>(&(static_cast<uint8_t*>(
836 buffer->backing_store())[i / 2])),
837 final_result);
838 }
839
840 // Handle remaining values
841 std::optional<uint8_t> result = 0;
842 for (size_t j = i; j < output_length * 2; j += 2) {
843 result = HandleRemainingHexValues(input_vector, j);
844 if (result.has_value()) {
845 static_cast<uint8_t*>(buffer->backing_store())[j / 2] = result.value();
846 } else {
847 return false;
848 }
849 }
850
851 return true;
852}
853#endif
854
855#ifdef NEON64
856
857inline std::optional<uint8x16_t> HexToUint8FastWithNeon(uint8x16_t nibbles) {
858 // Example:
859 // nibbles: (0x36, 0x66, 0x46, 0x32, 0x31, 0x32, 0x31, 0x32, 0x36, 0x66, 0x66,
860 // 0x32, 0x31, 0x32, 0x31, 0x66)
861
862 uint8x16_t char_0 = vdupq_n_u8('0');
863 uint8x16_t char_a = vdupq_n_u8('a');
864 uint8x16_t char_A = vdupq_n_u8('A');
865 uint8x16_t all_10 = vdupq_n_u8(10);
866 uint8x16_t all_6 = vdupq_n_u8(6);
867
868 // Create masks and nibbles for different character ranges
869 // Valid hexadecimal values are 0-9, a-f and A-F.
870 // mask_09 is 0xff when the corresponding value in nibbles is in range
871 // of 0 to 9. nibbles_09 is value-'0' and 0x0 for the rest of the values.
872 // Similar description apply to mask_af, mask_AF, nibbles_af and nibbles_af.
873
874 // mask_09: (0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
875 // 0xff, 0xff, 0xff, 0xff, 0x00)
876 // nibbles_09: (0x06, 0x00, 0x00, 0x02, 0x01, 0x02, 0x01, 0x02,
877 // 0x06, 0x00, 0x00, 0x02, 0x01, 0x02, 0x01, 0x00)
878 uint8x16_t nibbles_09 = vsubq_u8(nibbles, char_0);
879 uint8x16_t mask_09 = vcgtq_u8(all_10, nibbles_09);
880 nibbles_09 = vandq_u8(nibbles_09, mask_09);
881
882 // mask_af: (0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
883 // 0x00, 0x00, 0x00, 0x00, 0xff)
884 // nibbles_af: (0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
885 // 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x0f)
886 uint8x16_t nibbles_af = vsubq_u8(nibbles, char_a);
887 uint8x16_t mask_af = vcgtq_u8(all_6, nibbles_af);
888 nibbles_af = vandq_u8(vaddq_u8(nibbles_af, all_10), mask_af);
889
890 // mask_AF: (0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
891 // 0x00, 0x00, 0x00, 0x00, 0x00)
892 // nibbles_AF: (0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00,
893 // 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
894 uint8x16_t nibbles_AF = vsubq_u8(nibbles, char_A);
895 uint8x16_t mask_AF = vcgtq_u8(all_6, nibbles_AF);
896 nibbles_AF = vandq_u8(vaddq_u8(nibbles_AF, all_10), mask_AF);
897
898 // Combine masks to check if all nibbles are valid hex values
899 // (0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
900 // 0xff, 0xff, 0xff, 0xff)
901 uint8x16_t combined_mask = vorrq_u8(vorrq_u8(mask_af, mask_AF), mask_09);
902
903 // Check if all bytes are 0xFF
904 if (vminvq_u8(combined_mask) != 0xFF) return {};
905
906 // Combine the results using bitwise OR
907 // returns (0x06, 0x0f, 0x0f, 0x02, 0x01, 0x02, 0x01, 0x02, 0x06, 0x0f, 0x0f,
908 // 0x02, 0x01, 0x02, 0x01, 0x0f)
909 return vorrq_u8(vorrq_u8(nibbles_af, nibbles_AF), nibbles_09);
910}
911
912template <typename T>
913bool Uint8ArrayFromHexWithNeon(base::Vector<T>& input_vector,
914 DirectHandle<JSArrayBuffer> buffer,
915 size_t output_length) {
916 // Example: 666f6F6261726172666f6f62617261ff
917 CHECK_EQ(buffer->GetByteLength(), output_length);
918
919 size_t i;
920 for (i = 0; i + 32 <= output_length * 2; i += 32) {
921 // Load first batch of 16 hex characters into a Neon register
922 // (0x36, 0x36, 0x36, 0x66, 0x36, 0x46, 0x36, 0x32, 0x36, 0x31, 0x37, 0x32,
923 // 0x36, 0x31, 0x37, 0x32)
924 uint8x16_t first_batch =
925 vld1q_u8(reinterpret_cast<const uint8_t*>(&input_vector[i]));
926
927 // Handle TwoByteStrings
928 if constexpr (std::is_same_v<T, const base::uc16>) {
929 uint8x16_t second_part_first_batch =
930 vld1q_u8(reinterpret_cast<const uint8_t*>(&input_vector[i + 8]));
931 first_batch =
932 vmovn_high_u16(vmovn_u16(first_batch), second_part_first_batch);
933 }
934
935 // Load second batch of 16 hex characters into a Neon register
936 // (0x36, 0x36, 0x36, 0x66, 0x36, 0x66, 0x36, 0x32, 0x36, 0x31, 0x37, 0x32,
937 // 0x36, 0x31, 0x66, 0x66)
938 uint8x16_t second_batch =
939 vld1q_u8(reinterpret_cast<const uint8_t*>(&input_vector[i + 16]));
940
941 if constexpr (std::is_same_v<T, const base::uc16>) {
942 uint8x16_t second_part_second_batch =
943 vld1q_u8(reinterpret_cast<const uint8_t*>(&input_vector[i + 24]));
944 second_batch =
945 vmovn_high_u16(vmovn_u16(second_batch), second_part_second_batch);
946 }
947
948 // low nibbles are values with even indexes in fist_batch.
949 // (0x36, 0x00, 0x66, 0x00, 0x46, 0x00, 0x32, 0x00, 0x31, 0x00, 0x32, 0x00,
950 // 0x31, 0x00, 0x32, 0x00)
951 uint8x16_t first_batch_lo_nibbles =
952 vreinterpretq_u8_u16(vshrq_n_u16(vreinterpretq_u16_u8(first_batch), 8));
953
954 // low nibbles are values with even indexes in second_batch.
955 // (0x36, 0x00, 0x66, 0x00, 0x66, 0x00, 0x32, 0x00, 0x31, 0x00, 0x32, 0x00,
956 // 0x31, 0x00, 0x66, 0x00)
957 uint8x16_t second_batch_lo_nibbles = vreinterpretq_u8_u16(
958 vshrq_n_u16(vreinterpretq_u16_u8(second_batch), 8));
959
960 // Append low nibbles of first batch and second batch and remove 0x00s.
961 // (0x36, 0x66, 0x46, 0x32, 0x31, 0x32, 0x31, 0x32, 0x36, 0x66, 0x66, 0x32,
962 // 0x31, 0x32, 0x31, 0x66)
963 uint8x16_t lo_nibbles = vmovn_high_u16(vmovn_u16(first_batch_lo_nibbles),
964 second_batch_lo_nibbles);
965
966 // high nibbles are values with odd indexes in loaded batchs.
967 // vmovn_high_u16 and vmovn_u16 narrow input words by dropping most
968 // significant byte. (0x36, 0x36, 0x36, 0x36, 0x36, 0x37, 0x36, 0x37, 0x36,
969 // 0x36, 0x36, 0x36, 0x36, 0x37, 0x36, 0x66)
970 uint8x16_t hi_nibbles =
971 vmovn_high_u16(vmovn_u16(first_batch), second_batch);
972
973 // mapping low nibbles to uint8_t values.
974 // (0x06, 0x0f, 0x0f, 0x02, 0x01, 0x02, 0x01, 0x02, 0x06, 0x0f, 0x0f, 0x02,
975 // 0x01, 0x02, 0x01, 0x0f)
976 std::optional<uint8x16_t> maybe_uint8_low_nibbles =
977 HexToUint8FastWithNeon(lo_nibbles);
978
979 // Check if it is {} (includes invalid hex values)
980 if (!maybe_uint8_low_nibbles.has_value()) {
981 return false;
982 }
983 uint8x16_t uint8_low_nibbles = maybe_uint8_low_nibbles.value();
984
985 // mapping high nibbles to uint8_t values.
986 // (0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x06, 0x07, 0x06, 0x06, 0x06, 0x06,
987 // 0x06, 0x07, 0x06, 0x0f)
988 std::optional<uint8x16_t> maybe_uint8_high_nibbles =
989 HexToUint8FastWithNeon(hi_nibbles);
990
991 // Check if it is {} (includes invalid hex values)
992 if (!maybe_uint8_high_nibbles.has_value()) {
993 return false;
994 }
995 uint8x16_t uint8_high_nibbles = maybe_uint8_high_nibbles.value();
996
997 // shift uint8_t values of high nibbles to be able to combine with low
998 // uint8_t values.
999 // (0x60, 0x60, 0x60, 0x60, 0x60, 0x70, 0x60, 0x70, 0x60, 0x60, 0x60, 0x60,
1000 // 0x60, 0x70, 0x60, 0xf0)
1001 uint8x16_t uint8_shifted_high_nibbles =
1002 vshlq_n_u64(vreinterpretq_u64_u8(uint8_high_nibbles), 4);
1003
1004 // final result of combining pairs of uint8_t values of low and high
1005 // nibbles.
1006 // (0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72, 0x61, 0x72, 0x66, 0x6f,
1007 // 0x6f, 0x62, 0x61, 0x72, 0x61, 0xff)
1008 uint8x16_t final_result =
1009 vorrq_u8(uint8_shifted_high_nibbles, uint8_low_nibbles);
1010
1011 // store result in a buffer and it is equivalent to
1012 // [102,111,111,98,97,114,97,114,102,111,111,98,97,114,97,255]
1013 vst1q_u8(reinterpret_cast<uint8_t*>(buffer->backing_store()) + i / 2,
1014 final_result);
1015 }
1016
1017 // Handle remaining values
1018 std::optional<uint8_t> result = 0;
1019 for (size_t j = i; j < output_length * 2; j += 2) {
1020 result = HandleRemainingHexValues(input_vector, j);
1021 if (result.has_value()) {
1022 static_cast<uint8_t*>(buffer->backing_store())[j / 2] = result.value();
1023 } else {
1024 return false;
1025 }
1026 }
1027 return true;
1028}
1029
1030#endif
1031
1032} // namespace
1033
1034template <typename T>
1037 size_t output_length) {
1038 size_t input_length = input_vector.size();
1039 DCHECK_EQ(output_length, input_length / 2);
1040
1041#ifdef __SSE3__
1042 if (get_vectorization_kind() == SimdKinds::kAVX2 ||
1043 get_vectorization_kind() == SimdKinds::kSSE) {
1044 return Uint8ArrayFromHexWithSSE(input_vector, buffer, output_length);
1045 }
1046#endif
1047
1048#ifdef NEON64
1049 if (get_vectorization_kind() == SimdKinds::kNeon) {
1050 return Uint8ArrayFromHexWithNeon(input_vector, buffer, output_length);
1051 }
1052#endif
1053
1054 size_t index = 0;
1055 std::optional<uint8_t> result = 0;
1056 for (uint32_t i = 0; i < input_length; i += 2) {
1057 result = HandleRemainingHexValues(input_vector, i);
1058 if (result.has_value()) {
1059 reinterpret_cast<uint8_t*>(buffer->backing_store())[index++] =
1060 result.value();
1061 } else {
1062 return false;
1063 }
1064 }
1065 return true;
1066}
1067
1070 size_t output_length);
1073 size_t output_length);
1074
1075#ifdef NEON64
1076#undef NEON64
1077#endif
1078
1079} // namespace internal
1080} // namespace v8
#define T
Builtins::Kind kind
Definition builtins.cc:40
constexpr size_t size() const
Definition vector.h:70
static bool IsSupported(CpuFeature f)
static constexpr Tagged< Smi > FromInt(int value)
Definition smi.h:38
V8_INLINE constexpr StorageType ptr() const
ZoneVector< RpoNumber > & result
uint32_t const mask
int int32_t
Definition unicode.cc:40
unsigned short uint16_t
Definition unicode.cc:39
uint16_t uc16
Definition strings.h:18
Tagged(T object) -> Tagged< T >
V8_INLINE constexpr bool IsSmi(TaggedImpl< kRefType, StorageType > obj)
Definition objects.h:665
uintptr_t ArrayIndexOfIncludesDouble(Address array_start, uintptr_t array_len, uintptr_t from_index, Address search_element)
Definition simd.cc:442
kStaticElementsTemplateOffset kInstancePropertiesTemplateOffset Tagged< FixedArray >
Address Tagged_t
Definition globals.h:547
bool ArrayBufferFromHex(base::Vector< T > &input_vector, DirectHandle< JSArrayBuffer > buffer, size_t output_length)
Definition simd.cc:1035
uintptr_t ArrayIndexOfIncludesSmiOrObject(Address array_start, uintptr_t array_len, uintptr_t from_index, Address search_element)
Definition simd.cc:434
Tagged< Object > Uint8ArrayToHex(const char *bytes, size_t length, DirectHandle< SeqOneByteString > string_output)
Definition simd.cc:599
constexpr Opcode CMP
Tagged< To > Cast(Tagged< From > value, const v8::SourceLocation &loc=INIT_SOURCE_LOCATION_IN_DEBUG)
Definition casting.h:150
Maybe< T > Nothing()
Definition v8-maybe.h:112
Maybe< T > Just(const T &t)
Definition v8-maybe.h:117
#define VECTORIZED_LOOP_x86(type_load, type_eq, set1, cmp, movemask, extract)
Definition simd.cc:144
#define VECTORIZED_LOOP_Neon(type_load, type_eq, set1, cmp, movemask)
Definition simd.cc:130
#define V8_ALLOW_UNUSED
#define CHECK_EQ(lhs, rhs)
#define DCHECK(condition)
Definition logging.h:482
#define DCHECK_EQ(v1, v2)
Definition logging.h:485