Coverage Report

Created: 2026-07-25 14:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/util/simd/parquet_kernels.cpp
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
#include "util/simd/parquet_kernels.h"
19
20
#include <array>
21
#include <bit>
22
#include <cmath>
23
#include <cstring>
24
#include <type_traits>
25
26
#if defined(__x86_64__) && (defined(__GNUC__) || defined(__clang__))
27
#include <immintrin.h>
28
#define DORIS_PARQUET_X86_SIMD
29
#endif
30
31
namespace doris::simd {
32
namespace {
33
34
#ifdef DORIS_PARQUET_X86_SIMD
35
// Keep x86-only dispatch helpers out of scalar-only builds so warning-clean ARM builds do not
36
// depend on compiler-specific unused-function behavior.
37
12.8k
bool has_avx2() {
38
12.8k
    return __builtin_cpu_supports("avx2");
39
12.8k
}
40
41
void byte_stream_split_decode_scalar(const uint8_t* src, size_t width, size_t offset,
42
5
                                     size_t num_values, size_t stride, uint8_t* dest) {
43
13
    for (size_t row = 0; row < num_values; ++row) {
44
56
        for (size_t byte = 0; byte < width; ++byte) {
45
48
            dest[row * width + byte] = src[byte * stride + offset + row];
46
48
        }
47
8
    }
48
5
}
49
#endif
50
51
template <typename T>
52
80
bool scalar_compare(T lhs, T rhs, RawComparisonOp op) {
53
80
    const auto equal = [](T left, T right) {
54
7
        if constexpr (std::is_floating_point_v<T>) {
55
7
            return (std::isnan(left) && std::isnan(right)) || left == right;
56
7
        }
57
0
        return left == right;
58
7
    };
parquet_kernels.cpp:_ZZN5doris4simd12_GLOBAL__N_114scalar_compareIfEEbT_S3_NS0_15RawComparisonOpEENKUlffE_clEff
Line
Count
Source
53
7
    const auto equal = [](T left, T right) {
54
7
        if constexpr (std::is_floating_point_v<T>) {
55
7
            return (std::isnan(left) && std::isnan(right)) || left == right;
56
7
        }
57
0
        return left == right;
58
7
    };
Unexecuted instantiation: parquet_kernels.cpp:_ZZN5doris4simd12_GLOBAL__N_114scalar_compareIdEEbT_S3_NS0_15RawComparisonOpEENKUlddE_clEdd
Unexecuted instantiation: parquet_kernels.cpp:_ZZN5doris4simd12_GLOBAL__N_114scalar_compareIiEEbT_S3_NS0_15RawComparisonOpEENKUliiE_clEii
Unexecuted instantiation: parquet_kernels.cpp:_ZZN5doris4simd12_GLOBAL__N_114scalar_compareIlEEbT_S3_NS0_15RawComparisonOpEENKUlllE_clEll
59
80
    const auto greater = [](T left, T right) {
60
73
        if constexpr (std::is_floating_point_v<T>) {
61
            // Match Doris Compare: NaN is equal to NaN and greater than every finite value.
62
10
            if (std::isnan(right)) {
63
3
                return false;
64
3
            }
65
7
            if (std::isnan(left)) {
66
2
                return true;
67
2
            }
68
7
        }
69
5
        return left > right;
70
73
    };
parquet_kernels.cpp:_ZZN5doris4simd12_GLOBAL__N_114scalar_compareIfEEbT_S3_NS0_15RawComparisonOpEENKUlffE0_clEff
Line
Count
Source
59
10
    const auto greater = [](T left, T right) {
60
10
        if constexpr (std::is_floating_point_v<T>) {
61
            // Match Doris Compare: NaN is equal to NaN and greater than every finite value.
62
10
            if (std::isnan(right)) {
63
3
                return false;
64
3
            }
65
7
            if (std::isnan(left)) {
66
2
                return true;
67
2
            }
68
7
        }
69
5
        return left > right;
70
10
    };
Unexecuted instantiation: parquet_kernels.cpp:_ZZN5doris4simd12_GLOBAL__N_114scalar_compareIdEEbT_S3_NS0_15RawComparisonOpEENKUlddE0_clEdd
parquet_kernels.cpp:_ZZN5doris4simd12_GLOBAL__N_114scalar_compareIiEEbT_S3_NS0_15RawComparisonOpEENKUliiE0_clEii
Line
Count
Source
59
63
    const auto greater = [](T left, T right) {
60
        if constexpr (std::is_floating_point_v<T>) {
61
            // Match Doris Compare: NaN is equal to NaN and greater than every finite value.
62
            if (std::isnan(right)) {
63
                return false;
64
            }
65
            if (std::isnan(left)) {
66
                return true;
67
            }
68
        }
69
63
        return left > right;
70
63
    };
Unexecuted instantiation: parquet_kernels.cpp:_ZZN5doris4simd12_GLOBAL__N_114scalar_compareIlEEbT_S3_NS0_15RawComparisonOpEENKUlllE0_clEll
71
80
    switch (op) {
72
7
    case RawComparisonOp::EQ:
73
7
        return equal(lhs, rhs);
74
0
    case RawComparisonOp::NE:
75
0
        return !equal(lhs, rhs);
76
7
    case RawComparisonOp::LT:
77
7
        return greater(rhs, lhs);
78
3
    case RawComparisonOp::LE:
79
3
        return !greater(lhs, rhs);
80
53
    case RawComparisonOp::GT:
81
53
        return greater(lhs, rhs);
82
10
    case RawComparisonOp::GE:
83
10
        return !greater(rhs, lhs);
84
80
    }
85
0
    __builtin_unreachable();
86
80
}
parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_114scalar_compareIfEEbT_S3_NS0_15RawComparisonOpE
Line
Count
Source
52
17
bool scalar_compare(T lhs, T rhs, RawComparisonOp op) {
53
17
    const auto equal = [](T left, T right) {
54
17
        if constexpr (std::is_floating_point_v<T>) {
55
17
            return (std::isnan(left) && std::isnan(right)) || left == right;
56
17
        }
57
17
        return left == right;
58
17
    };
59
17
    const auto greater = [](T left, T right) {
60
17
        if constexpr (std::is_floating_point_v<T>) {
61
            // Match Doris Compare: NaN is equal to NaN and greater than every finite value.
62
17
            if (std::isnan(right)) {
63
17
                return false;
64
17
            }
65
17
            if (std::isnan(left)) {
66
17
                return true;
67
17
            }
68
17
        }
69
17
        return left > right;
70
17
    };
71
17
    switch (op) {
72
7
    case RawComparisonOp::EQ:
73
7
        return equal(lhs, rhs);
74
0
    case RawComparisonOp::NE:
75
0
        return !equal(lhs, rhs);
76
0
    case RawComparisonOp::LT:
77
0
        return greater(rhs, lhs);
78
3
    case RawComparisonOp::LE:
79
3
        return !greater(lhs, rhs);
80
7
    case RawComparisonOp::GT:
81
7
        return greater(lhs, rhs);
82
0
    case RawComparisonOp::GE:
83
0
        return !greater(rhs, lhs);
84
17
    }
85
0
    __builtin_unreachable();
86
17
}
Unexecuted instantiation: parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_114scalar_compareIdEEbT_S3_NS0_15RawComparisonOpE
parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_114scalar_compareIiEEbT_S3_NS0_15RawComparisonOpE
Line
Count
Source
52
63
bool scalar_compare(T lhs, T rhs, RawComparisonOp op) {
53
63
    const auto equal = [](T left, T right) {
54
63
        if constexpr (std::is_floating_point_v<T>) {
55
63
            return (std::isnan(left) && std::isnan(right)) || left == right;
56
63
        }
57
63
        return left == right;
58
63
    };
59
63
    const auto greater = [](T left, T right) {
60
63
        if constexpr (std::is_floating_point_v<T>) {
61
            // Match Doris Compare: NaN is equal to NaN and greater than every finite value.
62
63
            if (std::isnan(right)) {
63
63
                return false;
64
63
            }
65
63
            if (std::isnan(left)) {
66
63
                return true;
67
63
            }
68
63
        }
69
63
        return left > right;
70
63
    };
71
63
    switch (op) {
72
0
    case RawComparisonOp::EQ:
73
0
        return equal(lhs, rhs);
74
0
    case RawComparisonOp::NE:
75
0
        return !equal(lhs, rhs);
76
7
    case RawComparisonOp::LT:
77
7
        return greater(rhs, lhs);
78
0
    case RawComparisonOp::LE:
79
0
        return !greater(lhs, rhs);
80
46
    case RawComparisonOp::GT:
81
46
        return greater(lhs, rhs);
82
10
    case RawComparisonOp::GE:
83
10
        return !greater(rhs, lhs);
84
63
    }
85
0
    __builtin_unreachable();
86
63
}
Unexecuted instantiation: parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_114scalar_compareIlEEbT_S3_NS0_15RawComparisonOpE
87
88
#ifdef DORIS_PARQUET_X86_SIMD
89
template <size_t WIDTH>
90
__attribute__((target("avx2"))) void byte_stream_split_decode_avx2(const uint8_t* src,
91
                                                                   size_t offset, size_t num_values,
92
5
                                                                   size_t stride, uint8_t* dest) {
93
5
    static_assert(WIDTH == 4 || WIDTH == 8);
94
5
    constexpr size_t STEPS = WIDTH == 8 ? 3 : 2;
95
5
    constexpr size_t LANES = sizeof(__m256i);
96
5
    const size_t blocks = num_values / LANES;
97
5
    __m256i stage[STEPS + 1][WIDTH];
98
5
    __m256i result[WIDTH];
99
100
32.7k
    for (size_t block = 0; block < blocks; ++block) {
101
163k
        for (size_t stream = 0; stream < WIDTH; ++stream) {
102
131k
            stage[0][stream] = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(
103
131k
                    src + stream * stride + offset + block * LANES));
104
131k
        }
105
98.3k
        for (size_t step = 0; step < STEPS; ++step) {
106
            // AVX2 unpack instructions operate independently in each 128-bit half. Keep the
107
            // byte-transpose hierarchy lane-local, then stitch the halves only after the last
108
            // unpack so every output vector contains contiguous decoded rows.
109
196k
            for (size_t pair = 0; pair < WIDTH / 2; ++pair) {
110
131k
                stage[step + 1][pair * 2] =
111
131k
                        _mm256_unpacklo_epi8(stage[step][pair], stage[step][WIDTH / 2 + pair]);
112
131k
                stage[step + 1][pair * 2 + 1] =
113
131k
                        _mm256_unpackhi_epi8(stage[step][pair], stage[step][WIDTH / 2 + pair]);
114
131k
            }
115
65.5k
        }
116
98.3k
        for (size_t pair = 0; pair < WIDTH / 2; ++pair) {
117
65.5k
            result[pair] = _mm256_permute2x128_si256(stage[STEPS][pair * 2],
118
65.5k
                                                     stage[STEPS][pair * 2 + 1], 0x20);
119
65.5k
            result[WIDTH / 2 + pair] = _mm256_permute2x128_si256(stage[STEPS][pair * 2],
120
65.5k
                                                                 stage[STEPS][pair * 2 + 1], 0x31);
121
65.5k
        }
122
163k
        for (size_t lane = 0; lane < WIDTH; ++lane) {
123
131k
            _mm256_storeu_si256(reinterpret_cast<__m256i*>(dest + (block * WIDTH + lane) * LANES),
124
131k
                                result[lane]);
125
131k
        }
126
32.7k
    }
127
5
    const size_t processed = blocks * LANES;
128
5
    byte_stream_split_decode_scalar(src, WIDTH, offset + processed, num_values - processed, stride,
129
5
                                    dest + processed * WIDTH);
130
5
}
parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_129byte_stream_split_decode_avx2ILm4EEEvPKhmmmPh
Line
Count
Source
92
3
                                                                   size_t stride, uint8_t* dest) {
93
3
    static_assert(WIDTH == 4 || WIDTH == 8);
94
3
    constexpr size_t STEPS = WIDTH == 8 ? 3 : 2;
95
3
    constexpr size_t LANES = sizeof(__m256i);
96
3
    const size_t blocks = num_values / LANES;
97
3
    __m256i stage[STEPS + 1][WIDTH];
98
3
    __m256i result[WIDTH];
99
100
32.7k
    for (size_t block = 0; block < blocks; ++block) {
101
163k
        for (size_t stream = 0; stream < WIDTH; ++stream) {
102
131k
            stage[0][stream] = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(
103
131k
                    src + stream * stride + offset + block * LANES));
104
131k
        }
105
98.3k
        for (size_t step = 0; step < STEPS; ++step) {
106
            // AVX2 unpack instructions operate independently in each 128-bit half. Keep the
107
            // byte-transpose hierarchy lane-local, then stitch the halves only after the last
108
            // unpack so every output vector contains contiguous decoded rows.
109
196k
            for (size_t pair = 0; pair < WIDTH / 2; ++pair) {
110
131k
                stage[step + 1][pair * 2] =
111
131k
                        _mm256_unpacklo_epi8(stage[step][pair], stage[step][WIDTH / 2 + pair]);
112
131k
                stage[step + 1][pair * 2 + 1] =
113
131k
                        _mm256_unpackhi_epi8(stage[step][pair], stage[step][WIDTH / 2 + pair]);
114
131k
            }
115
65.5k
        }
116
98.3k
        for (size_t pair = 0; pair < WIDTH / 2; ++pair) {
117
65.5k
            result[pair] = _mm256_permute2x128_si256(stage[STEPS][pair * 2],
118
65.5k
                                                     stage[STEPS][pair * 2 + 1], 0x20);
119
65.5k
            result[WIDTH / 2 + pair] = _mm256_permute2x128_si256(stage[STEPS][pair * 2],
120
65.5k
                                                                 stage[STEPS][pair * 2 + 1], 0x31);
121
65.5k
        }
122
163k
        for (size_t lane = 0; lane < WIDTH; ++lane) {
123
131k
            _mm256_storeu_si256(reinterpret_cast<__m256i*>(dest + (block * WIDTH + lane) * LANES),
124
131k
                                result[lane]);
125
131k
        }
126
32.7k
    }
127
3
    const size_t processed = blocks * LANES;
128
3
    byte_stream_split_decode_scalar(src, WIDTH, offset + processed, num_values - processed, stride,
129
3
                                    dest + processed * WIDTH);
130
3
}
parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_129byte_stream_split_decode_avx2ILm8EEEvPKhmmmPh
Line
Count
Source
92
2
                                                                   size_t stride, uint8_t* dest) {
93
2
    static_assert(WIDTH == 4 || WIDTH == 8);
94
2
    constexpr size_t STEPS = WIDTH == 8 ? 3 : 2;
95
2
    constexpr size_t LANES = sizeof(__m256i);
96
2
    const size_t blocks = num_values / LANES;
97
2
    __m256i stage[STEPS + 1][WIDTH];
98
2
    __m256i result[WIDTH];
99
100
5
    for (size_t block = 0; block < blocks; ++block) {
101
27
        for (size_t stream = 0; stream < WIDTH; ++stream) {
102
24
            stage[0][stream] = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(
103
24
                    src + stream * stride + offset + block * LANES));
104
24
        }
105
12
        for (size_t step = 0; step < STEPS; ++step) {
106
            // AVX2 unpack instructions operate independently in each 128-bit half. Keep the
107
            // byte-transpose hierarchy lane-local, then stitch the halves only after the last
108
            // unpack so every output vector contains contiguous decoded rows.
109
45
            for (size_t pair = 0; pair < WIDTH / 2; ++pair) {
110
36
                stage[step + 1][pair * 2] =
111
36
                        _mm256_unpacklo_epi8(stage[step][pair], stage[step][WIDTH / 2 + pair]);
112
36
                stage[step + 1][pair * 2 + 1] =
113
36
                        _mm256_unpackhi_epi8(stage[step][pair], stage[step][WIDTH / 2 + pair]);
114
36
            }
115
9
        }
116
15
        for (size_t pair = 0; pair < WIDTH / 2; ++pair) {
117
12
            result[pair] = _mm256_permute2x128_si256(stage[STEPS][pair * 2],
118
12
                                                     stage[STEPS][pair * 2 + 1], 0x20);
119
12
            result[WIDTH / 2 + pair] = _mm256_permute2x128_si256(stage[STEPS][pair * 2],
120
12
                                                                 stage[STEPS][pair * 2 + 1], 0x31);
121
12
        }
122
27
        for (size_t lane = 0; lane < WIDTH; ++lane) {
123
24
            _mm256_storeu_si256(reinterpret_cast<__m256i*>(dest + (block * WIDTH + lane) * LANES),
124
24
                                result[lane]);
125
24
        }
126
3
    }
127
2
    const size_t processed = blocks * LANES;
128
2
    byte_stream_split_decode_scalar(src, WIDTH, offset + processed, num_values - processed, stride,
129
2
                                    dest + processed * WIDTH);
130
2
}
131
132
__attribute__((target("avx2"))) void delta_decode_int32_avx2(int32_t* values, size_t count,
133
                                                             int32_t min_delta,
134
12.5k
                                                             int32_t* last_value) {
135
12.5k
    const __m256i min_delta_vec = _mm256_set1_epi32(min_delta);
136
12.5k
    const size_t vector_count = count / 8 * 8;
137
192k
    for (size_t row = 0; row < vector_count; row += 8) {
138
180k
        __m256i value = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(values + row));
139
180k
        value = _mm256_add_epi32(value, min_delta_vec);
140
180k
        value = _mm256_add_epi32(value, _mm256_slli_si256(value, 4));
141
180k
        value = _mm256_add_epi32(value, _mm256_slli_si256(value, 8));
142
180k
        _mm256_storeu_si256(reinterpret_cast<__m256i*>(values + row), value);
143
180k
    }
144
12.5k
    __m128i carry = _mm_set1_epi32(*last_value);
145
373k
    for (size_t row = 0; row < vector_count; row += 4) {
146
360k
        __m128i value = _mm_loadu_si128(reinterpret_cast<const __m128i*>(values + row));
147
360k
        value = _mm_add_epi32(value, carry);
148
360k
        _mm_storeu_si128(reinterpret_cast<__m128i*>(values + row), value);
149
360k
        carry = _mm_shuffle_epi32(value, _MM_SHUFFLE(3, 3, 3, 3));
150
360k
    }
151
12.5k
    if (vector_count != 0) {
152
12.5k
        *last_value = _mm_cvtsi128_si32(carry);
153
12.5k
    }
154
12.5k
    using Unsigned = uint32_t;
155
13.3k
    for (size_t row = vector_count; row < count; ++row) {
156
740
        values[row] = static_cast<int32_t>(static_cast<Unsigned>(values[row]) +
157
740
                                           static_cast<Unsigned>(min_delta) +
158
740
                                           static_cast<Unsigned>(*last_value));
159
740
        *last_value = values[row];
160
740
    }
161
12.5k
}
162
163
__attribute__((target("avx2"))) void delta_decode_int64_avx2(int64_t* values, size_t count,
164
                                                             int64_t min_delta,
165
1
                                                             int64_t* last_value) {
166
1
    const __m256i min_delta_vec = _mm256_set1_epi64x(min_delta);
167
1
    const size_t vector_count = count / 4 * 4;
168
18
    for (size_t row = 0; row < vector_count; row += 4) {
169
17
        __m256i value = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(values + row));
170
17
        value = _mm256_add_epi64(value, min_delta_vec);
171
17
        value = _mm256_add_epi64(value, _mm256_slli_si256(value, 8));
172
17
        _mm256_storeu_si256(reinterpret_cast<__m256i*>(values + row), value);
173
17
    }
174
1
    __m128i carry = _mm_set1_epi64x(*last_value);
175
35
    for (size_t row = 0; row < vector_count; row += 2) {
176
34
        __m128i value = _mm_loadu_si128(reinterpret_cast<const __m128i*>(values + row));
177
34
        value = _mm_add_epi64(value, carry);
178
34
        _mm_storeu_si128(reinterpret_cast<__m128i*>(values + row), value);
179
34
        carry = _mm_unpackhi_epi64(value, value);
180
34
    }
181
1
    if (vector_count != 0) {
182
1
        *last_value = _mm_cvtsi128_si64(carry);
183
1
    }
184
1
    using Unsigned = uint64_t;
185
4
    for (size_t row = vector_count; row < count; ++row) {
186
3
        values[row] = static_cast<int64_t>(static_cast<Unsigned>(values[row]) +
187
3
                                           static_cast<Unsigned>(min_delta) +
188
3
                                           static_cast<Unsigned>(*last_value));
189
3
        *last_value = values[row];
190
3
    }
191
1
}
192
193
__attribute__((target("avx2"))) void dictionary_gather_avx2(const uint8_t* dictionary,
194
                                                            const uint32_t* indices, size_t count,
195
19
                                                            size_t value_width, uint8_t* dest) {
196
19
    size_t row = 0;
197
19
    if (value_width == 4) {
198
805
        for (; row + 8 <= count; row += 8) {
199
788
            const __m256i ids = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(indices + row));
200
788
            const __m256i gathered =
201
788
                    _mm256_i32gather_epi32(reinterpret_cast<const int*>(dictionary), ids, 4);
202
788
            _mm256_storeu_si256(reinterpret_cast<__m256i*>(dest + row * 4), gathered);
203
788
        }
204
17
    } else {
205
20
        for (; row + 4 <= count; row += 4) {
206
18
            const __m128i ids = _mm_loadu_si128(reinterpret_cast<const __m128i*>(indices + row));
207
18
            const __m256i gathered =
208
18
                    _mm256_i32gather_epi64(reinterpret_cast<const long long*>(dictionary), ids, 8);
209
18
            _mm256_storeu_si256(reinterpret_cast<__m256i*>(dest + row * 8), gathered);
210
18
        }
211
2
    }
212
36
    for (; row < count; ++row) {
213
17
        memcpy(dest + row * value_width, dictionary + indices[row] * value_width, value_width);
214
17
    }
215
19
}
216
217
template <size_t LANES>
218
0
constexpr auto make_expand_permute_lut() {
219
0
    std::array<std::array<int32_t, 8>, 1U << LANES> lut {};
220
0
    for (size_t mask = 0; mask < lut.size(); ++mask) {
221
0
        int32_t source = 0;
222
0
        for (size_t lane = 0; lane < LANES; ++lane) {
223
0
            const int32_t value = (mask & (1U << lane)) != 0 ? source++ : 0;
224
0
            if constexpr (LANES == 8) {
225
0
                lut[mask][lane] = value;
226
0
            } else {
227
0
                lut[mask][lane * 2] = value * 2;
228
0
                lut[mask][lane * 2 + 1] = value * 2 + 1;
229
0
            }
230
0
        }
231
0
    }
232
0
    return lut;
233
0
}
Unexecuted instantiation: parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_123make_expand_permute_lutILm8EEEDav
Unexecuted instantiation: parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_123make_expand_permute_lutILm4EEEDav
234
235
constexpr auto EXPAND_PERMUTE_32 = make_expand_permute_lut<8>();
236
constexpr auto EXPAND_PERMUTE_64 = make_expand_permute_lut<4>();
237
238
__attribute__((target("avx2"))) void expand_nullable_avx2(uint8_t* bytes, size_t compact_count,
239
                                                          const uint8_t* nulls, size_t output_count,
240
7
                                                          size_t value_width) {
241
7
    size_t source = compact_count;
242
7
    size_t output = output_count;
243
7
    if (value_width == 4) {
244
3
        auto* values = reinterpret_cast<int32_t*>(bytes);
245
9
        while (output >= 8) {
246
6
            const size_t start = output - 8;
247
6
            uint32_t valid_mask = 0;
248
54
            for (size_t lane = 0; lane < 8; ++lane) {
249
48
                valid_mask |= static_cast<uint32_t>(nulls[start + lane] == 0) << lane;
250
48
            }
251
6
            const size_t valid = std::popcount(valid_mask);
252
6
            source -= valid;
253
6
            const __m256i load_mask = _mm256_cmpgt_epi32(_mm256_set1_epi32(static_cast<int>(valid)),
254
6
                                                         _mm256_setr_epi32(0, 1, 2, 3, 4, 5, 6, 7));
255
6
            const __m256i compact = _mm256_maskload_epi32(values + source, load_mask);
256
6
            const __m256i permute = _mm256_loadu_si256(
257
6
                    reinterpret_cast<const __m256i*>(EXPAND_PERMUTE_32[valid_mask].data()));
258
6
            __m256i expanded = _mm256_permutevar8x32_epi32(compact, permute);
259
6
            const __m256i valid_lanes = _mm256_setr_epi32(
260
6
                    -(valid_mask & 1U), -((valid_mask >> 1) & 1U), -((valid_mask >> 2) & 1U),
261
6
                    -((valid_mask >> 3) & 1U), -((valid_mask >> 4) & 1U), -((valid_mask >> 5) & 1U),
262
6
                    -((valid_mask >> 6) & 1U), -((valid_mask >> 7) & 1U));
263
6
            expanded = _mm256_and_si256(expanded, valid_lanes);
264
6
            _mm256_storeu_si256(reinterpret_cast<__m256i*>(values + start), expanded);
265
6
            output = start;
266
6
        }
267
4
    } else {
268
4
        auto* values = reinterpret_cast<int64_t*>(bytes);
269
17
        while (output >= 4) {
270
13
            const size_t start = output - 4;
271
13
            uint32_t valid_mask = 0;
272
65
            for (size_t lane = 0; lane < 4; ++lane) {
273
52
                valid_mask |= static_cast<uint32_t>(nulls[start + lane] == 0) << lane;
274
52
            }
275
13
            const size_t valid = std::popcount(valid_mask);
276
13
            source -= valid;
277
13
            const __m256i load_mask =
278
13
                    _mm256_setr_epi64x(valid > 0 ? -1LL : 0, valid > 1 ? -1LL : 0,
279
13
                                       valid > 2 ? -1LL : 0, valid > 3 ? -1LL : 0);
280
13
            const __m256i compact = _mm256_maskload_epi64(
281
13
                    reinterpret_cast<const long long*>(values + source), load_mask);
282
13
            const __m256i permute = _mm256_loadu_si256(
283
13
                    reinterpret_cast<const __m256i*>(EXPAND_PERMUTE_64[valid_mask].data()));
284
13
            __m256i expanded = _mm256_permutevar8x32_epi32(compact, permute);
285
13
            const __m256i valid_lanes = _mm256_setr_epi64x(
286
13
                    (valid_mask & 1U) != 0 ? -1LL : 0, (valid_mask & 2U) != 0 ? -1LL : 0,
287
13
                    (valid_mask & 4U) != 0 ? -1LL : 0, (valid_mask & 8U) != 0 ? -1LL : 0);
288
13
            expanded = _mm256_and_si256(expanded, valid_lanes);
289
13
            _mm256_storeu_si256(reinterpret_cast<__m256i*>(values + start), expanded);
290
13
            output = start;
291
13
        }
292
4
    }
293
15
    while (output > 0) {
294
8
        --output;
295
8
        if (nulls[output] != 0) {
296
5
            memset(bytes + output * value_width, 0, value_width);
297
5
        } else {
298
3
            --source;
299
3
            memmove(bytes + output * value_width, bytes + source * value_width, value_width);
300
3
        }
301
8
    }
302
7
}
303
304
6
__attribute__((target("avx2"))) __m256i vector_all(__m256i) {
305
6
    return _mm256_set1_epi32(-1);
306
6
}
307
308
3
__attribute__((target("avx2"))) __m256 vector_all(__m256) {
309
3
    return _mm256_castsi256_ps(_mm256_set1_epi32(-1));
310
3
}
311
312
18
__attribute__((target("avx2"))) __m256d vector_all(__m256d) {
313
18
    return _mm256_castsi256_pd(_mm256_set1_epi32(-1));
314
18
}
315
316
3
__attribute__((target("avx2"))) __m256i vector_or(__m256i lhs, __m256i rhs) {
317
3
    return _mm256_or_si256(lhs, rhs);
318
3
}
319
320
1
__attribute__((target("avx2"))) __m256 vector_or(__m256 lhs, __m256 rhs) {
321
1
    return _mm256_or_ps(lhs, rhs);
322
1
}
323
324
6
__attribute__((target("avx2"))) __m256d vector_or(__m256d lhs, __m256d rhs) {
325
6
    return _mm256_or_pd(lhs, rhs);
326
6
}
327
328
1
__attribute__((target("avx2"))) __m256i vector_xor(__m256i lhs, __m256i rhs) {
329
1
    return _mm256_xor_si256(lhs, rhs);
330
1
}
331
332
0
__attribute__((target("avx2"))) __m256 vector_xor(__m256 lhs, __m256 rhs) {
333
0
    return _mm256_xor_ps(lhs, rhs);
334
0
}
335
336
2
__attribute__((target("avx2"))) __m256d vector_xor(__m256d lhs, __m256d rhs) {
337
2
    return _mm256_xor_pd(lhs, rhs);
338
2
}
339
340
template <typename Vec>
341
__attribute__((target("avx2"))) Vec combine_comparison(Vec equal, Vec greater, Vec less,
342
27
                                                       RawComparisonOp op) {
343
    // Target features do not propagate from AVX2 callers into separately instantiated helpers.
344
    // Keep the complete vector operation inside its own target scope for baseline x86 builds.
345
27
    const Vec all = vector_all(equal);
346
27
    switch (op) {
347
5
    case RawComparisonOp::EQ:
348
5
        return equal;
349
3
    case RawComparisonOp::NE:
350
3
        return vector_xor(equal, all);
351
6
    case RawComparisonOp::LT:
352
6
        return less;
353
5
    case RawComparisonOp::LE:
354
5
        return vector_or(less, equal);
355
3
    case RawComparisonOp::GT:
356
3
        return greater;
357
5
    case RawComparisonOp::GE:
358
5
        return vector_or(greater, equal);
359
27
    }
360
0
    __builtin_unreachable();
361
27
}
parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_118combine_comparisonIDv4_xEET_S4_S4_S4_NS0_15RawComparisonOpE
Line
Count
Source
342
6
                                                       RawComparisonOp op) {
343
    // Target features do not propagate from AVX2 callers into separately instantiated helpers.
344
    // Keep the complete vector operation inside its own target scope for baseline x86 builds.
345
6
    const Vec all = vector_all(equal);
346
6
    switch (op) {
347
0
    case RawComparisonOp::EQ:
348
0
        return equal;
349
1
    case RawComparisonOp::NE:
350
1
        return vector_xor(equal, all);
351
2
    case RawComparisonOp::LT:
352
2
        return less;
353
0
    case RawComparisonOp::LE:
354
0
        return vector_or(less, equal);
355
0
    case RawComparisonOp::GT:
356
0
        return greater;
357
3
    case RawComparisonOp::GE:
358
3
        return vector_or(greater, equal);
359
6
    }
360
0
    __builtin_unreachable();
361
6
}
parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_118combine_comparisonIDv8_fEET_S4_S4_S4_NS0_15RawComparisonOpE
Line
Count
Source
342
3
                                                       RawComparisonOp op) {
343
    // Target features do not propagate from AVX2 callers into separately instantiated helpers.
344
    // Keep the complete vector operation inside its own target scope for baseline x86 builds.
345
3
    const Vec all = vector_all(equal);
346
3
    switch (op) {
347
1
    case RawComparisonOp::EQ:
348
1
        return equal;
349
0
    case RawComparisonOp::NE:
350
0
        return vector_xor(equal, all);
351
0
    case RawComparisonOp::LT:
352
0
        return less;
353
1
    case RawComparisonOp::LE:
354
1
        return vector_or(less, equal);
355
1
    case RawComparisonOp::GT:
356
1
        return greater;
357
0
    case RawComparisonOp::GE:
358
0
        return vector_or(greater, equal);
359
3
    }
360
0
    __builtin_unreachable();
361
3
}
parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_118combine_comparisonIDv4_dEET_S4_S4_S4_NS0_15RawComparisonOpE
Line
Count
Source
342
18
                                                       RawComparisonOp op) {
343
    // Target features do not propagate from AVX2 callers into separately instantiated helpers.
344
    // Keep the complete vector operation inside its own target scope for baseline x86 builds.
345
18
    const Vec all = vector_all(equal);
346
18
    switch (op) {
347
4
    case RawComparisonOp::EQ:
348
4
        return equal;
349
2
    case RawComparisonOp::NE:
350
2
        return vector_xor(equal, all);
351
4
    case RawComparisonOp::LT:
352
4
        return less;
353
4
    case RawComparisonOp::LE:
354
4
        return vector_or(less, equal);
355
2
    case RawComparisonOp::GT:
356
2
        return greater;
357
2
    case RawComparisonOp::GE:
358
2
        return vector_or(greater, equal);
359
18
    }
360
0
    __builtin_unreachable();
361
18
}
362
363
__attribute__((target("avx2"))) void raw_compare_int32_avx2(const uint8_t* bytes, size_t count,
364
                                                            int32_t literal, RawComparisonOp op,
365
2
                                                            uint8_t* matches) {
366
2
    size_t row = 0;
367
2
    const __m256i rhs = _mm256_set1_epi32(literal);
368
4
    for (; row + 8 <= count; row += 8) {
369
2
        const __m256i lhs = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(bytes + row * 4));
370
2
        const __m256i equal = _mm256_cmpeq_epi32(lhs, rhs);
371
2
        const __m256i greater = _mm256_cmpgt_epi32(lhs, rhs);
372
2
        const __m256i less = _mm256_cmpgt_epi32(rhs, lhs);
373
2
        const uint32_t mask = static_cast<uint32_t>(_mm256_movemask_ps(
374
2
                _mm256_castsi256_ps(combine_comparison(equal, greater, less, op))));
375
18
        for (size_t lane = 0; lane < 8; ++lane) {
376
16
            matches[row + lane] &= static_cast<uint8_t>((mask >> lane) & 1U);
377
16
        }
378
2
    }
379
8
    for (; row < count; ++row) {
380
6
        int32_t value;
381
6
        memcpy(&value, bytes + row * 4, sizeof(value));
382
6
        bool keep = false;
383
6
        switch (op) {
384
0
        case RawComparisonOp::EQ:
385
0
            keep = value == literal;
386
0
            break;
387
3
        case RawComparisonOp::NE:
388
3
            keep = value != literal;
389
3
            break;
390
0
        case RawComparisonOp::LT:
391
0
            keep = value < literal;
392
0
            break;
393
0
        case RawComparisonOp::LE:
394
0
            keep = value <= literal;
395
0
            break;
396
0
        case RawComparisonOp::GT:
397
0
            keep = value > literal;
398
0
            break;
399
3
        case RawComparisonOp::GE:
400
3
            keep = value >= literal;
401
3
            break;
402
6
        }
403
6
        matches[row] &= static_cast<uint8_t>(keep);
404
6
    }
405
2
}
406
407
__attribute__((target("avx2"))) void raw_compare_int64_avx2(const uint8_t* bytes, size_t count,
408
                                                            int64_t literal, RawComparisonOp op,
409
2
                                                            uint8_t* matches) {
410
2
    size_t row = 0;
411
2
    const __m256i rhs = _mm256_set1_epi64x(literal);
412
6
    for (; row + 4 <= count; row += 4) {
413
4
        const __m256i lhs = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(bytes + row * 8));
414
4
        const __m256i equal = _mm256_cmpeq_epi64(lhs, rhs);
415
4
        const __m256i greater = _mm256_cmpgt_epi64(lhs, rhs);
416
4
        const __m256i less = _mm256_cmpgt_epi64(rhs, lhs);
417
4
        const uint32_t mask = static_cast<uint32_t>(_mm256_movemask_pd(
418
4
                _mm256_castsi256_pd(combine_comparison(equal, greater, less, op))));
419
20
        for (size_t lane = 0; lane < 4; ++lane) {
420
16
            matches[row + lane] &= static_cast<uint8_t>((mask >> lane) & 1U);
421
16
        }
422
4
    }
423
4
    for (; row < count; ++row) {
424
2
        int64_t value;
425
2
        memcpy(&value, bytes + row * 8, sizeof(value));
426
2
        bool keep = false;
427
2
        switch (op) {
428
0
        case RawComparisonOp::EQ:
429
0
            keep = value == literal;
430
0
            break;
431
0
        case RawComparisonOp::NE:
432
0
            keep = value != literal;
433
0
            break;
434
1
        case RawComparisonOp::LT:
435
1
            keep = value < literal;
436
1
            break;
437
0
        case RawComparisonOp::LE:
438
0
            keep = value <= literal;
439
0
            break;
440
0
        case RawComparisonOp::GT:
441
0
            keep = value > literal;
442
0
            break;
443
1
        case RawComparisonOp::GE:
444
1
            keep = value >= literal;
445
1
            break;
446
2
        }
447
2
        matches[row] &= static_cast<uint8_t>(keep);
448
2
    }
449
2
}
450
451
__attribute__((target("avx2"))) void raw_compare_float_avx2(const uint8_t* bytes, size_t count,
452
                                                            float literal, RawComparisonOp op,
453
3
                                                            uint8_t* matches) {
454
3
    size_t row = 0;
455
3
    const __m256 rhs = _mm256_set1_ps(literal);
456
6
    for (; row + 8 <= count; row += 8) {
457
3
        const __m256 lhs = _mm256_loadu_ps(reinterpret_cast<const float*>(bytes + row * 4));
458
3
        const __m256 lhs_nan = _mm256_cmp_ps(lhs, lhs, _CMP_UNORD_Q);
459
3
        const __m256 rhs_nan = _mm256_cmp_ps(rhs, rhs, _CMP_UNORD_Q);
460
3
        const __m256 both_nan = _mm256_and_ps(lhs_nan, rhs_nan);
461
3
        const __m256 equal = _mm256_or_ps(_mm256_cmp_ps(lhs, rhs, _CMP_EQ_OQ), both_nan);
462
3
        const __m256 greater = _mm256_or_ps(_mm256_cmp_ps(lhs, rhs, _CMP_GT_OQ),
463
3
                                            _mm256_andnot_ps(rhs_nan, lhs_nan));
464
3
        const __m256 less = _mm256_or_ps(_mm256_cmp_ps(lhs, rhs, _CMP_LT_OQ),
465
3
                                         _mm256_andnot_ps(lhs_nan, rhs_nan));
466
3
        const uint32_t mask = static_cast<uint32_t>(
467
3
                _mm256_movemask_ps(combine_comparison(equal, greater, less, op)));
468
27
        for (size_t lane = 0; lane < 8; ++lane) {
469
24
            matches[row + lane] &= static_cast<uint8_t>((mask >> lane) & 1U);
470
24
        }
471
3
    }
472
12
    for (; row < count; ++row) {
473
9
        float value;
474
9
        memcpy(&value, bytes + row * 4, sizeof(value));
475
9
        matches[row] &= static_cast<uint8_t>(scalar_compare(value, literal, op));
476
9
    }
477
3
}
478
479
__attribute__((target("avx2"))) void raw_compare_double_avx2(const uint8_t* bytes, size_t count,
480
                                                             double literal, RawComparisonOp op,
481
9
                                                             uint8_t* matches) {
482
9
    size_t row = 0;
483
9
    const __m256d rhs = _mm256_set1_pd(literal);
484
27
    for (; row + 4 <= count; row += 4) {
485
18
        const __m256d lhs = _mm256_loadu_pd(reinterpret_cast<const double*>(bytes + row * 8));
486
18
        const __m256d lhs_nan = _mm256_cmp_pd(lhs, lhs, _CMP_UNORD_Q);
487
18
        const __m256d rhs_nan = _mm256_cmp_pd(rhs, rhs, _CMP_UNORD_Q);
488
18
        const __m256d both_nan = _mm256_and_pd(lhs_nan, rhs_nan);
489
18
        const __m256d equal = _mm256_or_pd(_mm256_cmp_pd(lhs, rhs, _CMP_EQ_OQ), both_nan);
490
18
        const __m256d greater = _mm256_or_pd(_mm256_cmp_pd(lhs, rhs, _CMP_GT_OQ),
491
18
                                             _mm256_andnot_pd(rhs_nan, lhs_nan));
492
18
        const __m256d less = _mm256_or_pd(_mm256_cmp_pd(lhs, rhs, _CMP_LT_OQ),
493
18
                                          _mm256_andnot_pd(lhs_nan, rhs_nan));
494
18
        const uint32_t mask = static_cast<uint32_t>(
495
18
                _mm256_movemask_pd(combine_comparison(equal, greater, less, op)));
496
90
        for (size_t lane = 0; lane < 4; ++lane) {
497
72
            matches[row + lane] &= static_cast<uint8_t>((mask >> lane) & 1U);
498
72
        }
499
18
    }
500
9
    for (; row < count; ++row) {
501
0
        double value;
502
0
        memcpy(&value, bytes + row * 8, sizeof(value));
503
0
        matches[row] &= static_cast<uint8_t>(scalar_compare(value, literal, op));
504
0
    }
505
9
}
506
#endif
507
508
template <typename T>
509
132
void delta_decode_scalar(T* values, size_t count, T min_delta, T* last_value) {
510
132
    using Unsigned = std::make_unsigned_t<T>;
511
305
    for (size_t row = 0; row < count; ++row) {
512
173
        values[row] = static_cast<T>(static_cast<Unsigned>(values[row]) +
513
173
                                     static_cast<Unsigned>(min_delta) +
514
173
                                     static_cast<Unsigned>(*last_value));
515
173
        *last_value = values[row];
516
173
    }
517
132
}
parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_119delta_decode_scalarIiEEvPT_mS3_S4_
Line
Count
Source
509
132
void delta_decode_scalar(T* values, size_t count, T min_delta, T* last_value) {
510
132
    using Unsigned = std::make_unsigned_t<T>;
511
305
    for (size_t row = 0; row < count; ++row) {
512
173
        values[row] = static_cast<T>(static_cast<Unsigned>(values[row]) +
513
173
                                     static_cast<Unsigned>(min_delta) +
514
173
                                     static_cast<Unsigned>(*last_value));
515
173
        *last_value = values[row];
516
173
    }
517
132
}
Unexecuted instantiation: parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_119delta_decode_scalarIlEEvPT_mS3_S4_
518
519
template <typename T>
520
void raw_compare_scalar(const uint8_t* bytes, size_t count, T literal, RawComparisonOp op,
521
43
                        uint8_t* matches) {
522
117
    for (size_t row = 0; row < count; ++row) {
523
74
        if (matches[row] == 0) {
524
3
            continue;
525
3
        }
526
71
        T value;
527
71
        memcpy(&value, bytes + row * sizeof(T), sizeof(T));
528
71
        matches[row] = static_cast<uint8_t>(scalar_compare(value, literal, op));
529
71
    }
530
43
}
parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_118raw_compare_scalarIiEEvPKhmT_NS0_15RawComparisonOpEPh
Line
Count
Source
521
41
                        uint8_t* matches) {
522
107
    for (size_t row = 0; row < count; ++row) {
523
66
        if (matches[row] == 0) {
524
3
            continue;
525
3
        }
526
63
        T value;
527
63
        memcpy(&value, bytes + row * sizeof(T), sizeof(T));
528
63
        matches[row] = static_cast<uint8_t>(scalar_compare(value, literal, op));
529
63
    }
530
41
}
Unexecuted instantiation: parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_118raw_compare_scalarIlEEvPKhmT_NS0_15RawComparisonOpEPh
parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_118raw_compare_scalarIfEEvPKhmT_NS0_15RawComparisonOpEPh
Line
Count
Source
521
2
                        uint8_t* matches) {
522
10
    for (size_t row = 0; row < count; ++row) {
523
8
        if (matches[row] == 0) {
524
0
            continue;
525
0
        }
526
8
        T value;
527
8
        memcpy(&value, bytes + row * sizeof(T), sizeof(T));
528
8
        matches[row] = static_cast<uint8_t>(scalar_compare(value, literal, op));
529
8
    }
530
2
}
Unexecuted instantiation: parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_118raw_compare_scalarIdEEvPKhmT_NS0_15RawComparisonOpEPh
531
532
} // namespace
533
534
bool try_byte_stream_split_decode(const uint8_t* src, size_t width, size_t offset,
535
21
                                  size_t num_values, size_t stride, uint8_t* dest) {
536
21
#ifdef DORIS_PARQUET_X86_SIMD
537
21
    if (has_avx2() && num_values >= 32 && (width == 4 || width == 8)) {
538
5
        if (width == 4) {
539
3
            byte_stream_split_decode_avx2<4>(src, offset, num_values, stride, dest);
540
3
        } else {
541
2
            byte_stream_split_decode_avx2<8>(src, offset, num_values, stride, dest);
542
2
        }
543
5
        return true;
544
5
    }
545
16
#endif
546
16
    return false;
547
21
}
548
549
12.7k
void delta_decode(int32_t* values, size_t count, int32_t min_delta, int32_t* last_value) {
550
12.7k
#ifdef DORIS_PARQUET_X86_SIMD
551
12.7k
    if (has_avx2() && count >= 8) {
552
12.5k
        delta_decode_int32_avx2(values, count, min_delta, last_value);
553
12.5k
        return;
554
12.5k
    }
555
132
#endif
556
132
    delta_decode_scalar(values, count, min_delta, last_value);
557
132
}
558
559
1
void delta_decode(int64_t* values, size_t count, int64_t min_delta, int64_t* last_value) {
560
1
#ifdef DORIS_PARQUET_X86_SIMD
561
1
    if (has_avx2() && count >= 4) {
562
1
        delta_decode_int64_avx2(values, count, min_delta, last_value);
563
1
        return;
564
1
    }
565
0
#endif
566
0
    delta_decode_scalar(values, count, min_delta, last_value);
567
0
}
568
569
void dictionary_gather(const uint8_t* dictionary, const uint32_t* indices, size_t count,
570
19
                       size_t value_width, uint8_t* dest) {
571
19
#ifdef DORIS_PARQUET_X86_SIMD
572
19
    if (has_avx2() && ((value_width == 4 && count >= 8) || (value_width == 8 && count >= 4))) {
573
19
        dictionary_gather_avx2(dictionary, indices, count, value_width, dest);
574
19
        return;
575
19
    }
576
0
#endif
577
0
    for (size_t row = 0; row < count; ++row) {
578
0
        memcpy(dest + row * value_width, dictionary + indices[row] * value_width, value_width);
579
0
    }
580
0
}
581
582
void expand_nullable_values(uint8_t* values, size_t compact_count, const uint8_t* nulls,
583
11
                            size_t output_count, size_t value_width) {
584
11
#ifdef DORIS_PARQUET_X86_SIMD
585
11
    if (has_avx2() &&
586
11
        ((value_width == 4 && output_count >= 8) || (value_width == 8 && output_count >= 4))) {
587
        // Backward expansion is required because the compact input and expanded output alias.
588
        // Each SIMD block loads all of its source lanes before overwriting the wider destination.
589
7
        expand_nullable_avx2(values, compact_count, nulls, output_count, value_width);
590
7
        return;
591
7
    }
592
4
#endif
593
4
    size_t source = compact_count;
594
25
    for (size_t output = output_count; output > 0;) {
595
21
        --output;
596
21
        if (nulls[output] != 0) {
597
9
            memset(values + output * value_width, 0, value_width);
598
12
        } else {
599
12
            --source;
600
12
            memmove(values + output * value_width, values + source * value_width, value_width);
601
12
        }
602
21
    }
603
4
}
604
605
void raw_compare(const uint8_t* values, size_t count, int32_t literal, RawComparisonOp op,
606
43
                 uint8_t* matches) {
607
43
#ifdef DORIS_PARQUET_X86_SIMD
608
43
    if (has_avx2() && count >= 8) {
609
2
        raw_compare_int32_avx2(values, count, literal, op, matches);
610
2
        return;
611
2
    }
612
41
#endif
613
41
    raw_compare_scalar(values, count, literal, op, matches);
614
41
}
615
616
void raw_compare(const uint8_t* values, size_t count, int64_t literal, RawComparisonOp op,
617
2
                 uint8_t* matches) {
618
2
#ifdef DORIS_PARQUET_X86_SIMD
619
2
    if (has_avx2() && count >= 4) {
620
2
        raw_compare_int64_avx2(values, count, literal, op, matches);
621
2
        return;
622
2
    }
623
0
#endif
624
0
    raw_compare_scalar(values, count, literal, op, matches);
625
0
}
626
627
void raw_compare(const uint8_t* values, size_t count, float literal, RawComparisonOp op,
628
5
                 uint8_t* matches) {
629
5
#ifdef DORIS_PARQUET_X86_SIMD
630
5
    if (has_avx2() && count >= 8) {
631
        // Doris orders NaN above every finite value and considers NaN equal to NaN. The SIMD
632
        // masks deliberately reconstruct that total order instead of using ordered FP compares.
633
3
        raw_compare_float_avx2(values, count, literal, op, matches);
634
3
        return;
635
3
    }
636
2
#endif
637
2
    raw_compare_scalar(values, count, literal, op, matches);
638
2
}
639
640
void raw_compare(const uint8_t* values, size_t count, double literal, RawComparisonOp op,
641
9
                 uint8_t* matches) {
642
9
#ifdef DORIS_PARQUET_X86_SIMD
643
9
    if (has_avx2() && count >= 4) {
644
9
        raw_compare_double_avx2(values, count, literal, op, matches);
645
9
        return;
646
9
    }
647
0
#endif
648
0
    raw_compare_scalar(values, count, literal, op, matches);
649
0
}
650
651
} // namespace doris::simd