Skip to content

Commit 82e0805

Browse files
Merge pull request #3232 from KimYannn:feature/lookup64
PiperOrigin-RevId: 953262574
2 parents 67c3734 + 933c634 commit 82e0805

5 files changed

Lines changed: 140 additions & 9 deletions

File tree

g3doc/quick_reference.md

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2645,15 +2645,17 @@ The following `ReverseN` must not be called if `Lanes(D()) < N`:
26452645
`TwoTablesLookupLanes(a, b, indices)` on RVV/SVE if `Lanes(d) <
26462646
Lanes(DFromV<V>())`.
26472647
2648-
Each of the `Lookup8`, `Lookup16`, `Lookup32` (let $X denote the 8/16/32) ops
2649-
below return `GatherIndex(D(), tbl, indices)`, but are much more efficient, and
2650-
are limited to $X elements. Results are undefined if any indices are >= $X. They
2651-
are implemented using `TableLookupLanes` or `TwoTablesLookupLanes`. Let `T`
2652-
denote `TFromD<D>`. These ops are guaranteed to work if `D` is a full vector,
2653-
`HWY_TARGET != HWY_SCALAR` and `HWY_MIN_BYTES / sizeof(T) >= $X/2`. Use the
2654-
constexpr function `CanLookup$X(D())` to verify this. Even if it returns false,
2655-
the ops are still safe to call if `Lanes(D())` >= $X/2. Note that `tbl` must be
2656-
$X-element aligned!
2648+
Each of the `Lookup8`, `Lookup16`, `Lookup32`, `Lookup64` (let $X denote the
2649+
8/16/32/64) ops below return `GatherIndex(D(), tbl, indices)`, but are much more
2650+
efficient, and are limited to $X elements. Results are undefined if any indices
2651+
are >= $X. They are implemented using `TableLookupLanes` or
2652+
`TwoTablesLookupLanes`. Let `T` denote `TFromD<D>`. These ops are guaranteed to
2653+
work if `D` is a full vector, `HWY_TARGET != HWY_SCALAR` and
2654+
`HWY_MIN_BYTES / sizeof(T) >= $X/2`. `Lookup64` is also guaranteed for 128-bit
2655+
AArch64 NEON vectors if `T` is byte-sized. Use the constexpr function
2656+
`CanLookup$X(D())` to verify this. Even if it returns false, the ops are still
2657+
safe to call if `Lanes(D()) >= $X/2`. Note that `tbl` must be $X-element
2658+
aligned!
26572659
26582660
* `D`: {u,i,f}{16,32,64} \
26592661
<code>Vec&lt;D&gt; **Lookup8**(D, const TFromD&lt;D&gt;* tbl, VI
@@ -2667,6 +2669,10 @@ $X-element aligned!
26672669
<code>Vec&lt;D&gt; **Lookup32**(D, const TFromD&lt;D&gt;* tbl, VI
26682670
indices)</code>: as above, with $X = 32.
26692671
2672+
* `D`: {u,i}{8} \
2673+
<code>Vec&lt;D&gt; **Lookup64**(D, const TFromD&lt;D&gt;* tbl, VI
2674+
indices)</code>: as above, with $X = 64.
2675+
26702676
* <code>unspecified **IndicesFromVec**(D d, V idx)</code> prepares for
26712677
`TableLookupLanes` or `TwoTablesLookupLanes` with integer indices in `idx`,
26722678
which must be the same bit width as `TFromD<D>` and in the range `[0, 2 *

hwy/ops/arm_neon-inl.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6428,6 +6428,40 @@ HWY_API Vec128<T> TwoTablesLookupLanes(Vec128<T> a, Vec128<T> b,
64286428
#endif
64296429
}
64306430

6431+
// ------------------------------ Lookup64
6432+
6433+
#if HWY_ARCH_ARM_A64
6434+
6435+
// Per-target flag to prevent generic_ops-inl.h from defining Lookup64.
6436+
#ifdef HWY_NATIVE_LOOKUP64
6437+
#undef HWY_NATIVE_LOOKUP64
6438+
#else
6439+
#define HWY_NATIVE_LOOKUP64
6440+
#endif
6441+
6442+
template <class D, class VI, HWY_IF_UI8_D(D)>
6443+
HWY_INLINE VFromD<D> Lookup64(D d, const TFromD<D>* HWY_RESTRICT table,
6444+
VI indices) {
6445+
const DFromV<VI> di;
6446+
static_assert(sizeof(TFromD<D>) == sizeof(TFromD<decltype(di)>),
6447+
"Index/vector must have same lane size");
6448+
HWY_IF_CONSTEXPR(HWY_IS_DEBUG_BUILD) {
6449+
HWY_DASSERT(AllTrue(di, Lt(indices, Set(di, 64))));
6450+
}
6451+
6452+
const Full128<uint8_t> du8;
6453+
const auto t0 = Load(du8, reinterpret_cast<const uint8_t*>(table));
6454+
const auto t1 = Load(du8, reinterpret_cast<const uint8_t*>(table) + 16);
6455+
const auto t2 = Load(du8, reinterpret_cast<const uint8_t*>(table) + 32);
6456+
const auto t3 = Load(du8, reinterpret_cast<const uint8_t*>(table) + 48);
6457+
detail::Tuple4<uint8_t, 16> tables = {{{t0.raw, t1.raw, t2.raw, t3.raw}}};
6458+
const auto idx_u8 = ResizeBitCast(du8, indices);
6459+
return ResizeBitCast(
6460+
d, Vec128<uint8_t>{vqtbl4q_u8(tables.raw, idx_u8.raw)});
6461+
}
6462+
6463+
#endif // HWY_ARCH_ARM_A64
6464+
64316465
// ------------------------------ Reverse2 (CombineShiftRightBytes)
64326466

64336467
// Per-target flag to prevent generic_ops-inl.h defining 8-bit Reverse2/4/8.

hwy/ops/generic_ops-inl.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7286,6 +7286,52 @@ HWY_INLINE Vec<D> Lookup32(D d, const T* HWY_RESTRICT table, VI indices) {
72867286
}
72877287
}
72887288

7289+
// ------------------------------ Lookup64
7290+
7291+
#if (defined(HWY_NATIVE_LOOKUP64) == defined(HWY_TARGET_TOGGLE)) || HWY_IDE
7292+
#ifdef HWY_NATIVE_LOOKUP64
7293+
#undef HWY_NATIVE_LOOKUP64
7294+
#else
7295+
#define HWY_NATIVE_LOOKUP64
7296+
#endif
7297+
7298+
template <class D, typename T = TFromD<D>, class VI>
7299+
HWY_INLINE Vec<D> Lookup64(D d, const T* HWY_RESTRICT table, VI indices) {
7300+
const DFromV<VI> di;
7301+
static_assert(sizeof(T) == 1, "Lookup64 requires 8-bit table lanes");
7302+
static_assert(sizeof(T) == sizeof(TFromD<decltype(di)>),
7303+
"Index/vector must have same lane size");
7304+
HWY_IF_CONSTEXPR(HWY_IS_DEBUG_BUILD) {
7305+
HWY_DASSERT(Lanes(d) >= 32);
7306+
HWY_DASSERT(AllTrue(di, Lt(indices, Set(di, 64))));
7307+
}
7308+
7309+
HWY_IF_CONSTEXPR(!HWY_HAVE_SCALABLE) {
7310+
HWY_IF_CONSTEXPR(MaxLanes(d) >= 64) {
7311+
const CappedTag<T, 64> d64;
7312+
const Vec<D> t0 = ZeroExtendResizeBitCast(d, d64, Load(d64, table));
7313+
return TableLookupLanes(t0, IndicesFromVec(d, indices));
7314+
}
7315+
HWY_IF_CONSTEXPR(MaxLanes(d) < 64) {
7316+
const Vec<D> t0 = Load(d, table);
7317+
const Vec<D> t1 = Load(d, table + 32);
7318+
return TwoTablesLookupLanes(d, t0, t1, IndicesFromVec(d, indices));
7319+
}
7320+
}
7321+
7322+
HWY_IF_CONSTEXPR(HWY_HAVE_SCALABLE) {
7323+
const Vec<D> t0 = LoadN(d, table, 32);
7324+
const Vec<D> t1 = LoadN(d, table + 32, 32);
7325+
const Mask<decltype(di)> ge_32 = Ge(indices, Set(di, 32));
7326+
const VI idx_for_t1 = Sub(indices, Set(di, 32));
7327+
const Vec<D> r0 = TableLookupLanes(t0, IndicesFromVec(d, indices));
7328+
const Vec<D> r1 = TableLookupLanes(t1, IndicesFromVec(d, idx_for_t1));
7329+
return IfThenElse(RebindMask(d, ge_32), r1, r0);
7330+
}
7331+
}
7332+
7333+
#endif // HWY_NATIVE_LOOKUP64
7334+
72897335
// ------------------------------ Reverse2, Reverse4, Reverse8 (8-bit)
72907336

72917337
#if (defined(HWY_NATIVE_REVERSE2_8) == defined(HWY_TARGET_TOGGLE)) || HWY_IDE

hwy/ops/shared-inl.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,14 @@ HWY_API constexpr bool CanLookup32(D d) {
596596
(HWY_HAVE_SCALABLE && detail::IsFull(d) && (sizeof(T) == 1));
597597
}
598598

599+
// Returns whether `Lookup64` can definitely be used for vectors created from
600+
// tag `d`. May return a false negative for large scalable vectors.
601+
template <class D, typename T = TFromD<D>>
602+
HWY_API constexpr bool CanLookup64(D d) {
603+
return (!HWY_HAVE_SCALABLE && MaxLanes(d) >= 32) ||
604+
(HWY_ARCH_ARM_A64 && HWY_TARGET_IS_NEON && (sizeof(T) == 1));
605+
}
606+
599607
// ------------------------------ Choosing overloads (SFINAE)
600608

601609
// Same as base.h macros but with a Simd<T, N, kPow2> argument instead of T.

hwy/tests/table_test.cc

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,42 @@ HWY_NOINLINE void TestAllLookup32() {
366366
#endif
367367
}
368368

369+
struct TestLookup64 {
370+
template <class T, class D>
371+
HWY_NOINLINE void operator()(T /*unused*/, D d) {
372+
using V = Vec<D>;
373+
const RebindToUnsigned<D> du;
374+
using TU = TFromD<decltype(du)>;
375+
376+
const size_t N = Lanes(d);
377+
if (!CanLookup64(d)) return;
378+
379+
const size_t padded_N = HWY_MAX(N, 64);
380+
auto tbl = AllocateAligned<T>(padded_N);
381+
auto idx = AllocateAligned<TU>(padded_N);
382+
auto expected = AllocateAligned<T>(padded_N);
383+
HWY_ASSERT(tbl && idx && expected);
384+
385+
for (size_t i = 0; i < padded_N; ++i) {
386+
tbl[i] = ConvertScalarTo<T>(i + static_cast<size_t>(Unpredictable1()));
387+
}
388+
389+
HWY_ALIGN TU idx_source[16] = {0, 63, 1, 62, 15, 16, 31, 32,
390+
47, 48, 7, 56, 23, 40, 55, 8};
391+
for (size_t j = 0; j < padded_N; ++j) {
392+
idx[j] = static_cast<TU>(idx_source[j & 15]);
393+
expected[j] = ConvertScalarTo<T>(idx[j] + 1);
394+
}
395+
396+
const V actual = Lookup64(d, tbl.get(), Load(du, idx.get()));
397+
HWY_ASSERT_VEC_EQ(d, expected.get(), actual);
398+
}
399+
};
400+
401+
HWY_NOINLINE void TestAllLookup64() {
402+
ForUI8(ForPartialVectors<TestLookup64>());
403+
}
404+
369405
} // namespace
370406
// NOLINTNEXTLINE(google-readability-namespace-comments)
371407
} // namespace HWY_NAMESPACE
@@ -381,6 +417,7 @@ HWY_EXPORT_AND_TEST_P(HwyTableTest, TestAllTwoTablesLookupLanes);
381417
HWY_EXPORT_AND_TEST_P(HwyTableTest, TestAllLookup8);
382418
HWY_EXPORT_AND_TEST_P(HwyTableTest, TestAllLookup16);
383419
HWY_EXPORT_AND_TEST_P(HwyTableTest, TestAllLookup32);
420+
HWY_EXPORT_AND_TEST_P(HwyTableTest, TestAllLookup64);
384421
HWY_AFTER_TEST();
385422
} // namespace
386423
} // namespace hwy

0 commit comments

Comments
 (0)