aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/libs/hyperscan/src/rose/rose_internal.h
blob: 7bd6779c3d4198adf0767128efb1f327accabc0a (plain) (blame)
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
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
/*
 * Copyright (c) 2015-2019, Intel Corporation
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *  * Neither the name of Intel Corporation nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/** \file
 * \brief Rose data structures.
 */

#ifndef ROSE_INTERNAL_H
#define ROSE_INTERNAL_H

#include "ue2common.h"
#include "rose_common.h"
#include "util/scatter.h"

#define ROSE_OFFSET_INVALID          0xffffffff

// Group constants
typedef u64a rose_group;

// Delayed literal stuff
#define DELAY_BITS                  5
#define DELAY_SLOT_COUNT            (1U << DELAY_BITS)
#define MAX_DELAY                   (DELAY_SLOT_COUNT - 1)
#define DELAY_MASK                  (DELAY_SLOT_COUNT - 1)

/* Allocation of Rose literal ids
 *
 * The rose literal id space is segmented:
 *
 * ---- 0
 * |  | 'Normal' undelayed literals in either e or f tables
 * |  |
 * |  |
 * |  |
 * ---- anchored_base_id
 * |  | literals from the a table
 * |  |
 * ---- delay_base_id
 * |  | Delayed version of normal literals
 * |  |
 * ---- literalCount
 */

/* Rose Literal Sources
 *
 * Rose currently gets events (mainly roseProcessMatch calls) from a number of
 * sources:
 * 1) The floating table
 * 2) The anchored table
 * 3) Delayed literals
 * 4) Suffix NFAs
 * 5) Literal masks
 * 5) End anchored table
 * 6) Prefix / Infix nfas
 *
 * Care is required to ensure that events appear to come into Rose in order
 * (or sufficiently ordered for Rose to cope). Generally the progress of the
 * floating table is considered the canonical position in the buffer.
 *
 * Anchored table:
 * The anchored table is run before the floating table as nothing in it can
 * depend on a floating literal. Order is achieved by two measures:
 * a) user matches^1 are logged and held until the floating matcher passes that
 *    point;
 * b) any floating role with an anchored predecessor has a history relationship
 *    to enforce the ordering.
 *
 * Delayed literals:
 * Delayed literal ordering is handled by delivering any pending delayed
 * literals before processing any floating match.
 *
 * Suffix:
 * Suffixes are always pure terminal roles. Prior to raising a match^2, pending
 * NFA queues are run to the current point (floating or delayed literal) as
 * appropriate.
 *
 * Literal Masks:
 * These are triggered from either floating literals or delayed literals and
 * inspect the data behind them. Matches are raised at the same location as the
 * trigger literal so there are no ordering issues. Masks are always pure
 * terminal roles.
 *
 * Lookaround:
 * These are tests run on receipt of a role that "look around" the match,
 * checking characters at nearby offsets against reachability masks. Each role
 * can have a list of these lookaround offset/reach pairs, ordered in offset
 * order, and any failure will prevent the role from being switched on. Offsets
 * are relative to the byte after a literal match, and can be negative.
 *
 * Prefix / Infix:
 * TODO: remember / discuss
 *
 * End anchored table:
 * All user matches occur at the last byte. We do this last, so no problems
 * (yippee)
 *
 * ^1 User matches which occur before any possible match from the other tables
 *    are not delayed.
 * ^2 Queues may also be run to the current location if a queue is full and
 *    needs to be emptied.
 * ^3 There is no need to catch up at the end of a block scan as it contains no
 *    terminals.
 */

struct RoseCountingMiracle {
    char shufti; /** 1: count shufti class; 0: count a single character */
    u8 count; /** minimum number of occurrences for the counting
               * miracle char to kill the leftfix. */
    u8 c; /** character to look for if not shufti */
    u8 poison; /** character not in the shufti mask */
    m128 lo; /** shufti lo mask */
    m128 hi; /** shufti hi mask */
};

struct LeftNfaInfo {
    u32 maxQueueLen;
    u32 maxLag; // maximum of successor roles' lag
    u32 lagIndex; // iff lag != 0, index into leftfixLagTable
    u32 stopTable; // stop table index, or ROSE_OFFSET_INVALID
    u8 transient; /**< 0 if not transient, else max width of transient prefix */
    char infix; /* TODO: make flags */
    char eager; /**< nfa should be run eagerly to first match or death */
    char eod_check; /**< nfa is used by the event eod literal */
    u32 countingMiracleOffset; /** if not 0, offset to RoseCountingMiracle. */
    rose_group squash_mask; /* & mask applied when rose nfa dies */
};

struct NfaInfo {
    u32 nfaOffset;
    u32 stateOffset;
    u32 fullStateOffset; /* offset in scratch, relative to ??? */
    u32 ekeyListOffset; /* suffix, relative to base of rose, 0 if no ekeys */
    u8 no_retrigger; /* TODO */
    u8 in_sbmatcher;  /**< this outfix should not be run in small-block
                       * execution, as it will be handled by the sbmatcher
                       * HWLM table. */
    u8 eod; /* suffix is triggered by the etable --> can only produce eod
             * matches */
};

#define MAX_STORED_LEFTFIX_LAG 127 /* max leftfix lag that we can store in one
                                    * whole byte (OWB) (streaming only). Other
                                    * values in OWB are reserved for zombie
                                    * status */
#define OWB_ZOMBIE_ALWAYS_YES 128 /* nfa will always answer yes to any rose
                                   * prefix checks */

/* offset of the status flags in the stream state. */
#define ROSE_STATE_OFFSET_STATUS_FLAGS 0

/* offset of role mmbit in stream state (just after the status flag byte). */
#define ROSE_STATE_OFFSET_ROLE_MMBIT   sizeof(u8)

/**
 * \brief Rose state offsets.
 *
 * Stores pre-calculated offsets (in bytes) to MOST of the state structures
 * used by Rose, relative to the start of stream state.
 *
 * State not covered by this structure includes:
 *
 * -# the first byte, containing the status bitmask
 * -# the role state multibit
 */
struct RoseStateOffsets {
    /** History buffer.
     *
     * Max size of history is RoseEngine::historyRequired. */
    u32 history;

    /** Exhausted multibit.
     *
     * entry per exhaustible key (used by Highlander mode). If a bit is set,
     * reports with that ekey should not be delivered to the user. */
    u32 exhausted;

    /** size in bytes of exhausted multibit */
    u32 exhausted_size;

    /** Logical multibit.
     *
     * entry per logical key(operand/operator) (used by Logical Combination). */
    u32 logicalVec;

    /** size in bytes of logical multibit */
    u32 logicalVec_size;

    /** Combination multibit.
     *
     * entry per combination key (used by Logical Combination). */
    u32 combVec;

    /** size in bytes of combination multibit */
    u32 combVec_size;

    /** Multibit for active suffix/outfix engines. */
    u32 activeLeafArray;

    /** Size of multibit for active suffix/outfix engines in bytes. */
    u32 activeLeafArray_size;

    /** Multibit for active leftfix (prefix/infix) engines. */
    u32 activeLeftArray;

    /** Size of multibit for active leftfix (prefix/infix) engines in bytes. */
    u32 activeLeftArray_size;

    /** Table of lag information (stored as one byte per engine) for active
     * Rose leftfix engines. */
    u32 leftfixLagTable;

    /** State for anchored matchers (McClellan DFAs). */
    u32 anchorState;

    /** Packed Rose groups value. */
    u32 groups;

    /** Size of packed Rose groups value, in bytes. */
    u32 groups_size;

    /** State for long literal support. */
    u32 longLitState;

    /** Size of the long literal state. */
    u32 longLitState_size;

    /** Packed SOM location slots. */
    u32 somLocation;

    /** Multibit guarding SOM location slots. */
    u32 somValid;

    /** Multibit guarding SOM location slots. */
    u32 somWritable;

    /** Size of each of the somValid and somWritable multibits, in bytes. */
    u32 somMultibit_size;

    /** Begin of the region where NFA engine state is stored.
     * The NFA state region extends to end. */
    u32 nfaStateBegin;

    /** Total size of Rose state, in bytes. */
    u32 end;
};

struct RoseBoundaryReports {
    /** \brief 0 if no reports list, otherwise offset of program to run to
     * deliver reports at EOD. */
    u32 reportEodOffset;

    /** \brief 0 if no reports list, otherwise offset of program to run to
     * deliver reports at offset 0. */
    u32 reportZeroOffset;

    /** \brief 0 if no reports list, otherwise offset of program to run to
     * deliver reports if EOD is at offset 0. Superset of other programs. */
    u32 reportZeroEodOffset;
};

/* NFA Queue Assignment
 *
 * --- 0
 * (|) chained mpv (if present)
 *  #
 * --- outfixBeginQueue -
 *  | outfixes. enabled at offset 0.
 *  |
 *  #
 * --- outfixEndQueue -
 *  | suffixes. enabled by rose roles.
 *  |
 *  #
 * --- leftfixBeginQueue -
 *  | prefixes
 *  |
 *  #
 * --- ?
 *  | infixes
 *  |
 *  #
 */

#define ROSE_RUNTIME_FULL_ROSE     0
#define ROSE_RUNTIME_PURE_LITERAL  1
#define ROSE_RUNTIME_SINGLE_OUTFIX 2

/**
 * \brief Runtime structure header for Rose.
 *
 * Runtime structure header for Rose.
 * In memory, we follow this with:
 *   -# the "engine blob"
 *   -# anchored 'literal' matcher table
 *   -# floating literal matcher table
 *   -# eod-anchored literal matcher table
 *   -# small block table
 *   -# array of NFA offsets, one per queue
 *   -# array of state offsets, one per queue (+)
 *
 *  (+) stateOffset array note: Offsets in the array are either into the stream
 *  state (normal case) or into the tstate region of scratch (for transient rose
 *  nfas). Rose nfa info table can distinguish the cases.
 */
struct RoseEngine {
    u8  pureLiteral; /* Indicator of pure literal API */
    u8  noFloatingRoots; /* only need to run the anchored table if something
                          * matched in the anchored table */
    u8  requiresEodCheck; /* stuff happens at eod time */
    u8  hasOutfixesInSmallBlock; /**< has at least one outfix that must run even
                                    in small block scans. */
    u8  runtimeImpl; /**< can we just run the floating table or a single outfix?
                      * or do we need a full rose? */
    u8  mpvTriggeredByLeaf; /**< need to check (suf|out)fixes for mpv trigger */
    u8  canExhaust; /**< every pattern has an exhaustion key */
    u8  hasSom; /**< has at least one pattern which tracks SOM. */
    u8  somHorizon; /**< width in bytes of SOM offset storage (governed by
                        SOM precision) */
    u32 mode; /**< scanning mode, one of HS_MODE_{BLOCK,STREAM,VECTORED} */
    u32 historyRequired; /**< max amount of history required for streaming */
    u32 ekeyCount; /**< number of exhaustion keys */
    u32 lkeyCount; /**< number of logical keys */
    u32 lopCount; /**< number of logical ops */
    u32 ckeyCount; /**< number of combination keys */
    u32 logicalTreeOffset; /**< offset to mapping from lkey to LogicalOp */
    u32 combInfoMapOffset; /**< offset to mapping from ckey to combInfo */
    u32 dkeyCount; /**< number of dedupe keys */
    u32 dkeyLogSize; /**< size of fatbit for storing dkey log (bytes) */
    u32 invDkeyOffset; /**< offset to table mapping from dkeys to the external
                         *  report ids */
    u32 somLocationCount; /**< number of som locations required */
    u32 somLocationFatbitSize; /**< size of SOM location fatbit (bytes) */
    u32 rolesWithStateCount; // number of roles with entries in state bitset
    u32 stateSize; /* size of the state bitset
                    * WARNING: not the size of the rose state */
    u32 anchorStateSize; /* size of the state for the anchor dfas */
    u32 tStateSize; /* total size of the state for transient rose nfas */
    u32 scratchStateSize; /**< uncompressed state req'd for NFAs in scratch;
                           * used for sizing scratch only. */
    u32 smallWriteOffset; /**< offset of small-write matcher */
    u32 amatcherOffset; // offset of the anchored literal matcher (bytes)
    u32 ematcherOffset; // offset of the eod-anchored literal matcher (bytes)
    u32 fmatcherOffset; // offset of the floating literal matcher (bytes)
    u32 drmatcherOffset; // offset of the delayed rebuild table (bytes)
    u32 sbmatcherOffset; // offset of the small-block literal matcher (bytes)
    u32 longLitTableOffset; // offset of the long literal table
    u32 amatcherMinWidth; /**< minimum number of bytes required for a pattern
                           * involved with the anchored table to produce a full
                           * match. */
    u32 fmatcherMinWidth; /**< minimum number of bytes required for a pattern
                           * involved with the floating table to produce a full
                           * match. */
    u32 eodmatcherMinWidth; /**< minimum number of bytes required for a pattern
                               * involved with the eod table to produce a full
                               * match. */
    u32 amatcherMaxBiAnchoredWidth; /**< maximum number of bytes that can still
                                     * produce a match for a pattern involved
                                     * with the anchored table. */
    u32 fmatcherMaxBiAnchoredWidth; /**< maximum number of bytes that can still
                                     * produce a match for a pattern involved
                                     * with the anchored table. */

    /**
     * \brief Offset of u32 array of program offsets for reports used by
     * output-exposed engines.
     */
    u32 reportProgramOffset;

    /**
     * \brief Number of programs for reports used by output-exposed engines.
     */
    u32 reportProgramCount;

    /**
     * \brief Offset of u32 array of program offsets for delayed replay of
     * literals.
     */
    u32 delayProgramOffset;

    /**
     * \brief Offset of u32 array of program offsets for anchored literals.
     */
    u32 anchoredProgramOffset;

    u32 activeArrayCount; //number of nfas tracked in the active array
    u32 activeLeftCount; //number of nfas tracked in the active rose array
    u32 queueCount;      /**< number of nfa queues */
    u32 activeQueueArraySize; //!< size of fatbit for active queues (bytes)

    u32 eagerIterOffset; /**< offset to sparse iter for eager prefixes or 0 if
                          * none */

    /** \brief Number of keys used by CHECK_SET_HANDLED instructions in role
     * programs. */
    u32 handledKeyCount;

    /** \brief Size of the handled keys fatbit in scratch (bytes). */
    u32 handledKeyFatbitSize;

    u32 leftOffset;
    u32 roseCount;

    u32 eodProgramOffset; //!< EOD program, otherwise 0.
    u32 flushCombProgramOffset; /**< FlushCombination program, otherwise 0 */
    u32 lastFlushCombProgramOffset; /**< LastFlushCombination program,
                                     * otherwise 0 */

    u32 lastByteHistoryIterOffset; // if non-zero

    /** \brief Minimum number of bytes required to match. */
    u32 minWidth;

    /** \brief Minimum number of bytes required to match, excluding boundary
     * reports. */
    u32 minWidthExcludingBoundaries;

    u32 maxBiAnchoredWidth; /* ROSE_BOUND_INF if any non bianchored patterns
                             * present */
    u32 anchoredDistance; // region to run the anchored table over
    u32 anchoredMinDistance; /* start of region to run anchored table over */
    u32 floatingDistance; /* end of region to run the floating table over
                             ROSE_BOUND_INF if not bounded */
    u32 floatingMinDistance; /* start of region to run floating table over */
    u32 smallBlockDistance; /* end of region to run the floating table over
                               ROSE_BOUND_INF if not bounded */
    u32 floatingMinLiteralMatchOffset; /* the minimum offset that we can get a
                                        * 'valid' match from the floating
                                        * table */
    u32 nfaInfoOffset; /* offset to the nfa info offset array */
    rose_group initialGroups;
    rose_group floating_group_mask; /* groups that are used by the ftable */
    u32 size; // (bytes)
    u32 delay_count; /* number of delayed literal ids. */
    u32 delay_fatbit_size; //!< size of each delay fatbit in scratch (bytes)
    u32 anchored_count; /* number of anchored literal ids */
    u32 anchored_fatbit_size; //!< size of each anch fatbit in scratch (bytes)
    u32 maxFloatingDelayedMatch; /* max offset that a delayed literal can
                                  * usefully be reported */
    u32 delayRebuildLength; /* length of the history region which needs to be
                             * rescanned when we are doing a delayed literal
                             * rebuild scan. */
    struct RoseStateOffsets stateOffsets;
    struct RoseBoundaryReports boundary;
    u32 totalNumLiterals; /* total number of literals including dr */
    u32 asize; /* size of the atable */
    u32 outfixBeginQueue; /* first outfix queue */
    u32 outfixEndQueue; /* one past the last outfix queue */
    u32 leftfixBeginQueue; /* first prefix/infix queue */
    u32 initMpvNfa; /* (allegedly chained) mpv to force on at init */
    u32 rosePrefixCount; /* number of rose prefixes */
    u32 activeLeftIterOffset; /* mmbit_sparse_iter over non-transient roses */
    u32 ematcherRegionSize; /* max region size to pass to ematcher */
    u32 somRevCount; /**< number of som reverse nfas */
    u32 somRevOffsetOffset; /**< offset to array of offsets to som rev nfas */
    u32 longLitStreamState; // size in bytes

    struct scatter_full_plan state_init;
};

struct ALIGN_CL_DIRECTIVE anchored_matcher_info {
    u32 next_offset; /* relative to this, 0 for end */
    u32 state_offset; /* relative to anchorState */
    u32 anchoredMinDistance; /* start of region to run anchored table over */
};

/**
 * \brief Long literal subtable for a particular mode (caseful or nocase).
 */
struct RoseLongLitSubtable {
    /**
     * \brief Offset of the hash table (relative to RoseLongLitTable base).
     *
     * Offset is zero if no such table exists.
     */
    u32 hashOffset;

    /**
     * \brief Offset of the bloom filter (relative to RoseLongLitTable base).
     *
     * Offset is zero if no such table exists.
     */
    u32 bloomOffset;

    /** \brief lg2 of the size of the hash table. */
    u8 hashBits;

    /** \brief Size of the bloom filter in bits. */
    u8 bloomBits;

    /** \brief Number of bits of packed stream state used.  */
    u8 streamStateBits;
};

/**
 * \brief Long literal table header.
 */
struct RoseLongLitTable {
    /**
     * \brief Total size of the whole table (including strings, bloom filters,
     * hash tables).
     */
    u32 size;

    /** \brief Caseful sub-table (hash table and bloom filter). */
    struct RoseLongLitSubtable caseful;

    /** \brief Caseless sub-table (hash table and bloom filter). */
    struct RoseLongLitSubtable nocase;

    /** \brief Total size of packed stream state in bytes. */
    u8 streamStateBytes;

    /** \brief Max length of literal prefixes. */
    u8 maxLen;
};

/**
 * \brief One of these structures per hash table entry in our long literal
 * table.
 */
struct RoseLongLitHashEntry {
    /**
     * \brief Offset of the literal string itself, relative to
     * RoseLongLitTable base. Zero if this bucket is empty.
     */
    u32 str_offset;

    /** \brief Length of the literal string. */
    u32 str_len;
};

static really_inline
const struct anchored_matcher_info *getALiteralMatcher(
        const struct RoseEngine *t) {
    if (!t->amatcherOffset) {
        return NULL;
    }

    const char *lt = (const char *)t + t->amatcherOffset;
    assert(ISALIGNED_CL(lt));
    return (const struct anchored_matcher_info *)lt;
}

struct HWLM;

static really_inline
const struct HWLM *getFLiteralMatcher(const struct RoseEngine *t) {
    if (!t->fmatcherOffset) {
        return NULL;
    }

    const char *lt = (const char *)t + t->fmatcherOffset;
    assert(ISALIGNED_CL(lt));
    return (const struct HWLM *)lt;
}

static really_inline
const void *getSBLiteralMatcher(const struct RoseEngine *t) {
    if (!t->sbmatcherOffset) {
        return NULL;
    }

    const char *matcher = (const char *)t + t->sbmatcherOffset;
    assert(ISALIGNED_N(matcher, 8));
    return matcher;
}

static really_inline
const struct LeftNfaInfo *getLeftTable(const struct RoseEngine *t) {
    const struct LeftNfaInfo *r
        = (const struct LeftNfaInfo *)((const char *)t + t->leftOffset);
    assert(ISALIGNED_N(r, 4));
    return r;
}

struct mmbit_sparse_iter; // forward decl

static really_inline
const struct mmbit_sparse_iter *getActiveLeftIter(const struct RoseEngine *t) {
    assert(t->activeLeftIterOffset);
    const struct mmbit_sparse_iter *it = (const struct mmbit_sparse_iter *)
            ((const char *)t + t->activeLeftIterOffset);
    assert(ISALIGNED_N(it, 4));
    return it;
}

static really_inline
const struct NfaInfo *getNfaInfoByQueue(const struct RoseEngine *t, u32 qi) {
    const struct NfaInfo *infos
        = (const struct NfaInfo *)((const char *)t + t->nfaInfoOffset);
    assert(ISALIGNED_N(infos, sizeof(u32)));

    return &infos[qi];
}

static really_inline
const struct NFA *getNfaByInfo(const struct RoseEngine *t,
                               const struct NfaInfo *info) {
    return (const struct NFA *)((const char *)t + info->nfaOffset);
}

static really_inline
const struct NFA *getNfaByQueue(const struct RoseEngine *t, u32 qi) {
    const struct NfaInfo *info = getNfaInfoByQueue(t, qi);
    return getNfaByInfo(t, info);
}

static really_inline
u32 queueToLeftIndex(const struct RoseEngine *t, u32 qi) {
    assert(qi >= t->leftfixBeginQueue);
    return qi - t->leftfixBeginQueue;
}

static really_inline
const struct LeftNfaInfo *getLeftInfoByQueue(const struct RoseEngine *t,
                                             u32 qi) {
    const struct LeftNfaInfo *infos = getLeftTable(t);
    return &infos[queueToLeftIndex(t, qi)];
}

struct SmallWriteEngine;

static really_inline
const struct SmallWriteEngine *getSmallWrite(const struct RoseEngine *t) {
    if (!t->smallWriteOffset) {
        return NULL;
    }

    const struct SmallWriteEngine *smwr =
        (const struct SmallWriteEngine *)((const char *)t + t->smallWriteOffset);
    return smwr;
}

#endif // ROSE_INTERNAL_H