forked from rapidsai/cudf
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathregex.inl
More file actions
498 lines (450 loc) · 17.5 KB
/
Copy pathregex.inl
File metadata and controls
498 lines (450 loc) · 17.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
/*
* SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
#include <cudf/detail/utilities/integer_utils.hpp>
#include <cudf/strings/detail/char_tables.hpp>
#include <cudf/types.hpp>
namespace cudf {
namespace strings {
namespace detail {
/**
* @brief This holds the state information when evaluating a string
* against a regex pattern.
*
* There are 2 instances of this per string managed in the reljunk class.
* As each regex instruction is evaluated for a string, the result is
* reflected here. The regexec function updates and manages this state data.
*/
struct alignas(8) relist {
/**
* @brief Compute the memory size for the state data.
*/
CUDF_HOST_DEVICE constexpr inline static std::size_t data_size_for(int32_t insts)
{
return ((sizeof(ranges[0]) + sizeof(inst_ids[0])) * insts) +
cudf::util::div_rounding_up_unsafe(insts, 8);
}
/**
* @brief Compute the aligned memory allocation size.
*/
CUDF_HOST_DEVICE constexpr inline static std::size_t alloc_size(int32_t insts,
int32_t num_threads)
{
return cudf::util::round_up_unsafe<size_t>(data_size_for(insts) * num_threads, sizeof(restate));
}
struct alignas(16) restate {
int2 range;
int32_t inst_id;
int32_t reserved;
};
__device__ __forceinline__
relist(int16_t insts, int32_t num_threads, u_char* gp_ptr, int32_t index)
: masksize(cudf::util::div_rounding_up_unsafe(insts, 8)), stride(num_threads)
{
auto const rdata_size = sizeof(ranges[0]);
auto const idata_size = sizeof(inst_ids[0]);
ranges = reinterpret_cast<decltype(ranges)>(gp_ptr + (index * rdata_size));
inst_ids =
reinterpret_cast<int16_t*>(gp_ptr + (rdata_size * stride * insts) + (index * idata_size));
mask = gp_ptr + ((rdata_size + idata_size) * stride * insts) + (index * masksize);
}
__device__ __forceinline__ void reset()
{
memset(mask, 0, masksize);
size = 0;
}
template <positional P = positional::BEGIN_END>
__device__ __forceinline__ bool activate(int32_t id, int32_t begin, int32_t end)
{
if (readMask(id)) { return false; }
writeMask(id);
inst_ids[size * stride] = static_cast<int16_t>(id);
if constexpr (P == positional::BEGIN_END) { ranges[size * stride] = int2{begin, end}; }
++size;
return true;
}
template <positional P = positional::BEGIN_END>
[[nodiscard]] __device__ __forceinline__ restate get_state(int16_t idx) const
{
if constexpr (P == positional::BEGIN_END) {
return restate{ranges[idx * stride], inst_ids[idx * stride]};
}
return restate{{-1, -1}, inst_ids[idx * stride]};
}
[[nodiscard]] __device__ __forceinline__ int16_t get_size() const { return size; }
private:
int16_t size{};
int16_t const masksize;
int32_t const stride;
int2* __restrict__ ranges; // pair per instruction
int16_t* __restrict__ inst_ids; // one per instruction
u_char* __restrict__ mask; // bit per instruction
__device__ __forceinline__ void writeMask(int32_t pos) const
{
u_char const uc = 1 << (pos & 7);
mask[pos >> 3] |= uc;
}
[[nodiscard]] __device__ __forceinline__ bool readMask(int32_t pos) const
{
u_char const uc = mask[pos >> 3];
return static_cast<bool>((uc >> (pos & 7)) & 1);
}
};
template <positional P = positional::BEGIN_END>
struct reljunk {
relist* __restrict__ list1;
relist* __restrict__ list2;
int32_t starttype{};
char32_t startchar{};
__device__ inline reljunk(relist* list1, relist* list2, reinst const inst)
: list1(list1), list2(list2)
{
if (inst.type == CHAR || inst.type == BOL) {
starttype = inst.type;
startchar = inst.u1.c;
}
}
__device__ inline void swaplist()
{
auto tmp = list1;
list1 = list2;
list2 = tmp;
}
};
/**
* @brief Check for supported new-line characters
*
* '\n, \r, \u0085, \u2028, or \u2029'
*/
CUDF_HOST_DEVICE constexpr bool is_newline(char32_t const ch)
{
return (ch == '\n' || ch == '\r' || ch == 0x00c285 || ch == 0x00e280a8 || ch == 0x00e280a9);
}
/**
* @brief Utility to check a specific character against this class instance.
*
* @param ch A 4-byte UTF-8 character.
* @param codepoint_flags Used for mapping a character to type for builtin classes.
* @return true if the character matches
*/
__device__ __forceinline__ bool reclass_device::is_match(char32_t const ch,
uint8_t const* codepoint_flags) const
{
for (int i = 0; i < count; ++i) {
auto const literal = literals[i];
if ((ch >= literal.first) && (ch <= literal.last)) { return true; }
}
if (!builtins) return false;
uint32_t codept = utf8_to_codepoint(ch);
if (codept > 0x00'FFFF) return false;
int8_t fl = codepoint_flags[codept];
if ((builtins & CCLASS_W) && ((ch == '_') || IS_ALPHANUM(fl))) // \w
return true;
if ((builtins & CCLASS_S) && IS_SPACE(fl)) // \s
return true;
if ((builtins & CCLASS_D) && IS_DIGIT(fl)) // \d
return true;
if ((builtins & NCCLASS_W) && ((ch != '\n') && (ch != '_') && !IS_ALPHANUM(fl))) // \W
return true;
if ((builtins & NCCLASS_S) && !IS_SPACE(fl)) // \S
return true;
if ((builtins & NCCLASS_D) && ((ch != '\n') && !IS_DIGIT(fl))) // \D
return true;
//
return false;
}
__device__ __forceinline__ reinst reprog_device::get_inst(int32_t id) const { return _insts[id]; }
__device__ __forceinline__ reclass_device reprog_device::get_class(int32_t id) const
{
return _classes[id];
}
__device__ __forceinline__ bool reprog_device::is_empty() const
{
return insts_counts() == 0 || get_inst(0).type == END;
}
__device__ __forceinline__ void reprog_device::store(void* buffer) const
{
if (_prog_size > MAX_SHARED_MEM) { return; }
auto ptr = static_cast<u_char*>(buffer);
// create instance inside the given buffer
auto result = new (ptr) reprog_device(*this);
// add the insts array
ptr += sizeof(reprog_device);
auto insts = reinterpret_cast<reinst*>(ptr);
result->_insts = insts;
for (int idx = 0; idx < _insts_count; ++idx)
*insts++ = _insts[idx];
// add the startinst_ids array
ptr += cudf::util::round_up_unsafe(_insts_count * sizeof(_insts[0]), sizeof(_startinst_ids[0]));
auto ids = reinterpret_cast<int32_t*>(ptr);
result->_startinst_ids = ids;
for (int idx = 0; idx < _starts_count; ++idx)
*ids++ = _startinst_ids[idx];
// add the classes array
ptr += cudf::util::round_up_unsafe(_starts_count * sizeof(int32_t), sizeof(_classes[0]));
auto classes = reinterpret_cast<reclass_device*>(ptr);
result->_classes = classes;
// fill in each class
auto d_ptr = reinterpret_cast<reclass_range*>(classes + _classes_count);
for (int idx = 0; idx < _classes_count; ++idx) {
classes[idx] = _classes[idx];
classes[idx].literals = d_ptr;
for (int jdx = 0; jdx < _classes[idx].count; ++jdx)
*d_ptr++ = _classes[idx].literals[jdx];
}
}
__device__ __forceinline__ reprog_device reprog_device::load(reprog_device const prog, void* buffer)
{
return (prog._prog_size > MAX_SHARED_MEM) ? reprog_device(prog)
: reinterpret_cast<reprog_device*>(buffer)[0];
}
__device__ __forceinline__ static string_view::const_iterator find_char(
cudf::char_utf8 chr, string_view const d_str, string_view::const_iterator itr)
{
while (itr.byte_offset() < d_str.size_bytes() && *itr != chr) {
++itr;
}
return itr;
}
/**
* @brief Evaluate a specific string against regex pattern compiled to this instance.
*
* This is the main function for executing the regex against an individual string.
*
* @param dstr String used for matching.
* @param jnk State data object for this string.
* @param[in,out] begin Character position to start evaluation. On return, it is the position of the
* match.
* @param[in,out] end Character position to stop evaluation. On return, it is the end of the matched
* substring.
* @param group_id Index of the group to match in a multi-group regex pattern.
* @return >0 if match found
*/
template <positional P>
__device__ __forceinline__ match_result reprog_device::regexec(string_view const dstr,
reljunk<P>& jnk,
string_view::const_iterator itr,
cudf::size_type end,
cudf::size_type const group_id) const
{
int32_t match = 0;
auto begin = itr.position();
auto pos = begin;
auto eos = end;
auto checkstart = jnk.starttype != 0;
auto last_character = false;
jnk.list1->reset();
do {
// fast check for first CHAR or BOL
if (checkstart) {
auto startchar = static_cast<char_utf8>(jnk.startchar);
switch (jnk.starttype) {
case BOL: {
if (pos == 0) { break; }
if (startchar != '^' && startchar != 'S') { return cuda::std::nullopt; }
if (startchar != '\n') { break; }
--itr;
startchar = static_cast<char_utf8>('\n');
[[fallthrough]];
}
case CHAR: {
auto const find_itr = find_char(startchar, dstr, itr);
if (find_itr.byte_offset() >= dstr.size_bytes()) { return cuda::std::nullopt; }
itr = find_itr + (jnk.starttype == BOL);
pos = itr.position();
break;
}
}
}
if (((eos < 0) || (pos < eos)) && match == 0) {
auto ids = _startinst_ids;
while (*ids >= 0) {
jnk.list1->template activate<P>(*ids++, (group_id == 0 ? pos : -1), -1);
}
}
last_character = itr.byte_offset() >= dstr.size_bytes();
char_utf8 const c = last_character ? 0 : *itr;
// expand the non-character types like: LBRA, RBRA, BOL, EOL, BOW, NBOW, and OR
bool expanded = false;
do {
jnk.list2->reset();
expanded = false;
for (int16_t i = 0; i < jnk.list1->get_size(); i++) {
auto state = jnk.list1->template get_state<P>(i);
auto range = state.range;
auto const inst = get_inst(state.inst_id);
int32_t id_activate = -1;
switch (inst.type) {
case CHAR:
case ANY:
case ANYNL:
case CCLASS:
case NCCLASS:
case END: id_activate = state.inst_id; break;
case LBRA:
if (inst.u1.subid == group_id) { range.x = pos; }
id_activate = inst.u2.next_id;
expanded = true;
break;
case RBRA:
if (inst.u1.subid == group_id) { range.y = pos; }
id_activate = inst.u2.next_id;
expanded = true;
break;
case BOL: {
auto titr = itr;
auto const prev_c = pos > 0 ? *(--titr) : 0;
// For EXT_NEWLINE, \r\n is a single terminator: ^ matches AFTER the \n and
// never between the \r and \n.
if ((pos == 0) || ((inst.u1.c == '^') && (prev_c == '\n')) ||
((inst.u1.c == 'S') && is_newline(prev_c) && !((prev_c == '\r') && (c == '\n')))) {
id_activate = inst.u2.next_id;
expanded = true;
}
break;
}
case EOL: {
// EOL matches at end-of-string, or before a line terminator (MULTILINE: any
// terminator; otherwise: only the final terminator). For EXT_NEWLINE the
// two-character CRLF (\r\n) is treated as a SINGLE terminator: '$' matches before
// the \r and never between \r and \n.
if (last_character) {
id_activate = inst.u2.next_id;
expanded = true;
break;
}
bool const ext = (inst.u1.c == 'S' || inst.u1.c == 'N');
bool const nl = ext ? is_newline(c) : (c == '\n');
if (nl && (inst.u1.c != 'Z')) {
// For EXT_NEWLINE, suppress a match wedged between the CR and LF of a CRLF.
auto titr = itr;
bool mid_crlf = ext && (c == '\n') && (pos > 0) && (*(--titr) == '\r');
if (!mid_crlf) {
// MULTILINE matches before any terminator; otherwise only the final one.
bool matched = (inst.u1.c == '$' || inst.u1.c == 'S');
if (!matched) {
matched = (itr.byte_offset() + bytes_in_char_utf8(c) == dstr.size_bytes());
if (!matched && ext && (c == '\r')) {
// a CR beginning a trailing CRLF also ends the string
auto nitr = itr;
++nitr;
matched =
(nitr.byte_offset() < dstr.size_bytes()) && (*nitr == '\n') &&
(nitr.byte_offset() + bytes_in_char_utf8(static_cast<char_utf8>('\n')) ==
dstr.size_bytes());
}
}
if (matched) {
id_activate = inst.u2.next_id;
expanded = true;
}
}
}
break;
}
case BOW:
case NBOW: {
auto titr = itr;
auto const prev_c = pos > 0 ? *(--titr) : 0;
auto const word_class = reclass_device{CCLASS_W};
bool const curr_is_word = word_class.is_match(c, _codepoint_flags);
bool const prev_is_word = word_class.is_match(prev_c, _codepoint_flags);
if ((curr_is_word == prev_is_word) != (inst.type == BOW)) {
id_activate = inst.u2.next_id;
expanded = true;
}
break;
}
case OR:
jnk.list2->template activate<P>(inst.u1.right_id, range.x, range.y);
id_activate = inst.u2.left_id;
expanded = true;
break;
}
if (id_activate >= 0) { jnk.list2->template activate<P>(id_activate, range.x, range.y); }
}
jnk.swaplist();
} while (expanded);
// execute instructions
bool continue_execute = true;
jnk.list2->reset();
for (int16_t i = 0; continue_execute && i < jnk.list1->get_size(); i++) {
auto const state = jnk.list1->template get_state<P>(i);
auto const range = state.range;
auto const inst = get_inst(state.inst_id);
int32_t id_activate = -1;
switch (inst.type) {
case CHAR:
if (inst.u1.c == c) id_activate = inst.u2.next_id;
break;
case ANY: {
if ((c == '\n') || ((inst.u1.c == 'N') && is_newline(c))) { break; }
[[fallthrough]];
}
case ANYNL: id_activate = inst.u2.next_id; break;
case NCCLASS:
case CCLASS: {
auto const cls = get_class(inst.u1.cls_id);
if (cls.is_match(static_cast<char32_t>(c), _codepoint_flags) == (inst.type == CCLASS)) {
id_activate = inst.u2.next_id;
}
break;
}
case END:
match = 1;
begin = range.x;
end = group_id == 0 ? pos : range.y;
// done with execute
continue_execute = false;
break;
}
if (continue_execute && (id_activate >= 0)) {
jnk.list2->template activate<P>(id_activate, range.x, range.y);
}
}
++pos;
++itr;
jnk.swaplist();
checkstart = jnk.list1->get_size() == 0;
} while (!last_character && (!checkstart || !match));
return match ? match_result({begin, end}) : cuda::std::nullopt;
}
template <positional P>
__device__ __forceinline__ match_result reprog_device::find(int32_t const thread_idx,
string_view const dstr,
string_view::const_iterator begin,
cudf::size_type end) const
{
return call_regexec<P>(thread_idx, dstr, begin, end);
}
__device__ __forceinline__ match_result reprog_device::extract(int32_t const thread_idx,
string_view const dstr,
string_view::const_iterator begin,
cudf::size_type end,
cudf::size_type const group_id) const
{
end = begin.position() + 1;
auto const result = call_regexec(thread_idx, dstr, begin, end, group_id + 1);
// a capture group that did not participate in the overall match
// (e.g. an unmatched optional group) has an invalid range
return (result && (result->first >= 0)) ? result : cuda::std::nullopt;
}
template <positional P>
__device__ __forceinline__ match_result
reprog_device::call_regexec(int32_t const thread_idx,
string_view const dstr,
string_view::const_iterator begin,
cudf::size_type end,
cudf::size_type const group_id) const
{
auto gp_ptr = reinterpret_cast<u_char*>(_buffer);
relist list1(static_cast<int16_t>(_max_insts), _thread_count, gp_ptr, thread_idx);
gp_ptr += relist::alloc_size(_max_insts, _thread_count);
relist list2(static_cast<int16_t>(_max_insts), _thread_count, gp_ptr, thread_idx);
reljunk<P> jnk(&list1, &list2, get_inst(_startinst_id));
return regexec<P>(dstr, jnk, begin, end, group_id);
}
} // namespace detail
} // namespace strings
} // namespace cudf