<<
path:
root/public/blog.git/html/modules/uniform/program.jai
blob: 103161f849abdae7d79f7906f275dd34effb2366
[raw]
[clear marker]
4 kInstAlt :: 0; // choose between out_ and out1_
5 kInstAltMatch; // Alt: out_ is [00-FF] and back, out1_ is match; or vice versa.
6 kInstByteRange; // next (possible case-folded) byte must be in [lo_, hi_]
7 kInstCapture; // capturing parenthesis number cap_
8 kInstEmptyWidth; // empty-width special (^ $ ...); bit(s) set in empty_
9 kInstMatch; // found a match!
10 kInstNop; // no-op; occasionally unavoidable
11 kInstFail; // never match; occasionally unavoidable
14// Bit flags for empty-width specials
15EmptyOp :: enum_flags {
16 kEmptyBeginLine :: 1<<0; // ^ - beginning of line
17 kEmptyEndLine :: 1<<1; // $ - end of line
18 kEmptyBeginText :: 1<<2; // \A - beginning of text
19 kEmptyEndText :: 1<<3; // \z - end of text
20 kEmptyWordBoundary :: 1<<4; // \b - word boundary
21 kEmptyNonWordBoundary :: 1<<5; // \B - not \b
22 kEmptyAllFlags :: (1<<6)-1;
25// Single instruction in regexp program.
28 // int id(Prog* p) { return static_cast<int>(this - p->inst_.data()); }
29 // int out1() { DCHECK(opcode() == kInstAlt || opcode() == kInstAltMatch); return out1_; }
30 // int cap() { DCHECK_EQ(opcode(), kInstCapture); return cap_; }
31 // int lo() { DCHECK_EQ(opcode(), kInstByteRange); return lo_; }
32 // int hi() { DCHECK_EQ(opcode(), kInstByteRange); return hi_; }
33 // int foldcase() { DCHECK_EQ(opcode(), kInstByteRange); return hint_foldcase_&1; }
34 // int hint() { DCHECK_EQ(opcode(), kInstByteRange); return hint_foldcase_>>1; }
35 // int match_id() { DCHECK_EQ(opcode(), kInstMatch); return match_id_; }
36 // EmptyOp empty() { DCHECK_EQ(opcode(), kInstEmptyWidth); return empty_; }
38 // Maximum instruction id.
39 // (Must fit in out_opcode_. PatchList/last steal another bit.)
40 MAX_INST :: (1<<28) - 1;
42 out_opcode: u32; // 28 bits: out, 1 bit: last, 3 (low) bits: opcode
43 union { // additional instruction arguments:
44 out1: u32; // opcode == kInstAlt
45 // alternate next instruction
46 cap: s32; // opcode == kInstCapture
47 // Index of capture register (holds text
48 // position recorded by capturing parentheses).
49 // For \n (the submatch for the nth parentheses),
50 // the left parenthesis captures into register 2*n
51 // and the right one captures into register 2*n+1.
53 match_id: s32; // opcode == kInstMatch
54 // Match ID to identify this match (for re2::Set).
56 struct { // opcode == kInstByteRange
57 lo: u8; // byte range is lo_-hi_ inclusive
59 hint_foldcase: u16; // 15 bits: hint, 1 (low) bit: foldcase
60 // hint to execution engines: the delta to the
61 // next instruction (in the current list) worth
62 // exploring iff this instruction matched; 0
63 // means there are no remaining possibilities,
64 // which is most likely for character classes.
65 // foldcase: A-Z -> a-z before checking range.
68 empty: EmptyOp; // opcode == kInstEmptyWidth
69 // empty_ is bitwise OR of kEmpty* flags above.
73get_opcode :: (using inst: Inst) -> InstOp {
74 return cast(InstOp) (out_opcode & 7);
77get_last :: (using inst: Inst) -> bool {
78 return cast(bool)((out_opcode >> 3) & 1);
81get_out :: (using inst: Inst) -> u32 {
82 return out_opcode >> 4;
85set_opcode :: (using inst: *Inst, opcode: InstOp) {
86 out_opcode = out_opcode & 0xFFFF_FFF8 | cast(u32)opcode;
89set_last :: (using inst: *Inst) {
90 out_opcode = out_opcode & 0xFFFF_FFF7 | cast(u32)(1 << 3);
93set_out :: (using inst: *Inst, out: int) {
94 out_opcode = out_opcode & 0x0F | cast(u32)(out << 4);
97set_out_opcode :: (using inst: *Inst, out: int, opcode: InstOp) {
98 out_opcode = out_opcode & 0x08 | cast(u32)(out << 4) | cast(u32)opcode;
102get_foldcase :: (using inst: Inst) -> bool {
103 assert(get_opcode(inst) == .kInstByteRange);
104 return cast(bool) (hint_foldcase & 1);
107get_hint :: (using inst: Inst) -> int {
108 assert(get_opcode(inst) == .kInstByteRange);
109 return hint_foldcase >> 1;
112greedy :: (using inst: Inst, p: *Prog) -> bool {
113 assert(get_opcode(inst) == .kInstAltMatch);
114 out_inst := p.inst[get_out(inst)];
115 return get_opcode(out_inst) == .kInstByteRange ||
116 (get_opcode(out_inst) == .kInstNop && get_opcode(p.inst[get_out(out_inst)]) == .kInstByteRange);
119// Does this inst (an kInstByteRange) match c?
120// @ToDo: should this int be Rune?
121matches :: (using inst: Inst, c: int) -> bool {
122 assert(get_opcode(inst) == .kInstByteRange);
123 if (get_foldcase(inst) && #char "A" <= c && c <= #char "Z") {
124 c += #char "a" - #char "A";
126 return lo <= c && c <= hi;
130// Returns whether byte c is a word character: ASCII only.
131// Used by the implementation of \b and \B.
132// This is not right for Unicode, but:
133// - it's hard to get right in a byte-at-a-time matching world
134// (the DFA has only one-byte lookahead).
135// - even if the lookahead were possible, the Progs would be huge.
136// This crude approximation is the same one PCRE uses.
137is_word_char :: (c: u8) -> bool {
138 return (#char "A" <= c && c <= #char "Z") ||
139 (#char "a" <= c && c <= #char "z") ||
140 (#char "0" <= c && c <= #char "9") ||
144// Compiled form of regexp program.
146 // Whether to anchor the search.
148 UNANCHORED; // match anywhere
149 ANCHORED; // match only starting at beginning of text
152 // Kind of match to look for (for anchor != kFullMatch)
154 // kLongestMatch mode finds the overall longest
155 // match but still makes its submatch choices the way
156 // Perl would, not in the way prescribed by POSIX.
157 // The POSIX rules are much more expensive to implement,
158 // and no one has needed them.
160 // kFullMatch is not strictly necessary -- we could use
161 // kLongestMatch and then check the length of the match -- but
162 // the matching code can run faster if it knows to consider only
165 kFirstMatch; // like Perl, PCRE
166 kLongestMatch; // like egrep or POSIX
167 kFullMatch; // match only entire text; implies anchor==ANCHORED
168 kManyMatch; // for SearchDFA, records set of matches
171 anchor_start: bool; // regexp has explicit start anchor
172 anchor_end: bool; // regexp has explicit end anchor
173 reversed: bool; // whether program runs backward over input
174 did_flatten: bool; // has Flatten been called?
175 did_onepass: bool; // has IsOnePass been called?
177 start: int; // entry point for program
178 start_unanchored: int; // unanchored entry point for program
179 bytemap_range: int; // bytemap_[x] < bytemap_range_
180 prefix_size: int; // size of prefix (0 if no prefix)
181 prefix_front: s16 = -1; // first byte of prefix (-1 if no prefix)
182 prefix_back: s16 = -1; // last byte of prefix (-1 if no prefix)
184 list_count: int; // count of lists (see above)
185 inst_count: [#run enum_highest_value(InstOp) + 1] int; // count of instructions by opcode
186 list_heads: [..] u16; // sparse array enumerating list heads
187 // not populated if size_ is overly large
189 inst: [..] Inst; // pointer to instruction array
190 onepass_nodes: [..] u8; // data for OnePass nodes
192 dfa_mem: s64; // Maximum memory for DFAs.
193 // dfa_first: *DFA; // DFA cached for kFirstMatch/kManyMatch
194 // dfa_longest: *DFA; // DFA cached for kLongestMatch/kFullMatch
196 bytemap: [256] u8; // map from input bytes to byte classes
198 // std::once_flag dfa_first_once_;
199 // std::once_flag dfa_longest_once_;
202 // Inst *inst(int id) { return &inst_[id]; }
203 // int start() { return start_; }
204 // void set_start(int start) { start_ = start; }
205 // int start_unanchored() { return start_unanchored_; }
206 // void set_start_unanchored(int start) { start_unanchored_ = start; }
207 // int size() { return size_; }
208 // bool reversed() { return reversed_; }
209 // void set_reversed(bool reversed) { reversed_ = reversed; }
210 // int list_count() { return list_count_; }
211 // int inst_count(InstOp op) { return inst_count_[op]; }
212 // uint16_t* list_heads() { return list_heads_.data(); }
213 // int64_t dfa_mem() { return dfa_mem_; }
214 // void set_dfa_mem(int64_t dfa_mem) { dfa_mem_ = dfa_mem; }
215 // bool anchor_start() { return anchor_start_; }
216 // void set_anchor_start(bool b) { anchor_start_ = b; }
217 // bool anchor_end() { return anchor_end_; }
218 // void set_anchor_end(bool b) { anchor_end_ = b; }
219 // int bytemap_range() { return bytemap_range_; }
220 // const uint8_t* bytemap() { return bytemap_; }
221 // bool can_prefix_accel() { return prefix_size_ != 0; }
223 // // Accelerates to the first likely occurrence of the prefix.
224 // // Returns a pointer to the first byte or NULL if not found.
225 // const void* PrefixAccel(const void* data, size_t size) {
226 // DCHECK_GE(prefix_size_, 1);
227 // return prefix_size_ == 1 ? memchr(data, prefix_front_, size)
228 // : PrefixAccel_FrontAndBack(data, size);
233can_bit_state :: (using prog: *Prog) -> bool {
234 return list_heads.count > 0;
237init_alt :: (inst: *Inst, out: u32, out1: u32) {
238 assert(inst.out_opcode == 0);
239 set_out_opcode(inst, out, .kInstAlt);
243init_byte_range :: (inst: *Inst, lo: u8, hi: u8, foldcase: bool, out: int) {
244 assert(inst.out_opcode == 0);
245 set_out_opcode(inst, out, .kInstByteRange);
248 inst.hint_foldcase = (cast(u16)foldcase)&1;
251init_capture :: (inst: *Inst, cap: s32, out: u32) {
252 assert(inst.out_opcode == 0);
253 set_out_opcode(inst, out, .kInstCapture);
257init_empty_width :: (inst: *Inst, empty: EmptyOp, out: u32) {
258 assert(inst.out_opcode == 0);
259 set_out_opcode(inst, out, .kInstEmptyWidth);
263init_match :: (inst: *Inst, id: s32) {
264 assert(inst.out_opcode == 0);
265 set_opcode(inst, .kInstMatch);
269init_nop :: (inst: *Inst) {
270 assert(inst.out_opcode == 0);
271 set_opcode(inst, .kInstNop);
274init_fail :: (inst: *Inst) {
275 assert(inst.out_opcode == 0);
276 set_opcode(inst, .kInstFail);
279// Peep-hole optimizer.
280optimize :: (using prog: *Prog) {
282 init(*q, inst.count);
284 // Eliminate nops. Most are taken out during compilation
285 // but a few are hard to avoid.
287 add_if_nonzero(*q, start);
291 // ToDo: Why is this a pointer????
296 if get_opcode(jp.*) != .kInstNop break;
300 add_if_nonzero(*q, j);
301 if get_opcode(ip.*) == .kInstAlt {
306 if get_opcode(jp.*) != .kInstNop break;
310 add_if_nonzero(*q, j);
314 // Insert kInstAltMatch instructions
317 // j: ByteRange [00-FF] -> ip
319 // or the reverse (the above is the greedy one).
320 // Rewrite Alt to AltMatch.
322 add_if_nonzero(*q, start);
325 add_if_nonzero(*q, get_out(ip.*));
326 if get_opcode(ip.*) == .kInstAlt {
327 add_if_nonzero(*q, ip.out1);
329 // ToDo: Why are these pointers????
330 j := inst[get_out(ip.*)];
332 if get_opcode(j) == .kInstByteRange && get_out(j) == id && j.lo == 0x00 && j.hi == 0xFF && is_match(prog, k) {
333 set_opcode(ip, .kInstAltMatch);
336 if is_match(prog, j) && get_opcode(k) == .kInstByteRange && get_out(k) == id && k.lo == 0x00 && k.hi == 0xFF {
337 set_opcode(ip, .kInstAltMatch);
343add_if_nonzero :: (set: *Sparse_Set, i: int) {
349// Is ip a guaranteed match at end of text, perhaps after some capturing?
350is_match :: (prog: *Prog, i_in: Inst) -> bool {
353 if #complete get_opcode(i) == {
354 case .kInstAlt; #through;
355 case .kInstAltMatch; #through;
356 case .kInstByteRange; #through;
357 case .kInstFail; #through;
358 case .kInstEmptyWidth;
361 case .kInstCapture; #through;
363 i = prog.inst[get_out(i)];
369 // Should not be needed
374empty_flags :: (text: string, pos: int) -> EmptyOp {
379 flags |= EmptyOp.kEmptyBeginText | .kEmptyBeginLine;
380 } else if text[pos - 1] == #char "\n" {
381 flags |= .kEmptyBeginLine;
385 if pos == text.count {
386 flags |= EmptyOp.kEmptyEndText | .kEmptyEndLine;
387 } else if text[pos] == #char "\n" { // @ToDo: I removed a pos < count check here. Can pos ever be greater than count??
388 flags |= .kEmptyEndLine;
392 if pos == 0 && text.count == 0 {
393 // no word boundary here
395 if is_word_char(text[pos]) {
396 flags |= .kEmptyWordBoundary;
398 } else if pos == text.count {
399 if is_word_char(text[pos-1]) {
400 flags |= .kEmptyWordBoundary;
403 if (is_word_char(text[pos-1]) != is_word_char(text[pos])) {
404 flags |= .kEmptyWordBoundary;
407 if !(flags & .kEmptyWordBoundary) {
408 flags |= .kEmptyNonWordBoundary;
414compute_byte_map :: (using prog: *Prog) {
415 // Fill in bytemap with byte classes for the program.
416 // Ranges of bytes that are treated indistinguishably
417 // will be mapped to a single byte class.
418 builder: ByteMapBuilder;
421 // Don't repeat the work for ^ and $.
422 marked_line_boundaries := false;
423 // Don't repeat the work for \b and \B.
424 marked_word_boundaries := false;
426 for id: 0..inst.count - 1 {
428 if get_opcode(ip.*) == .kInstByteRange {
431 mark(*builder, lo, hi);
432 if get_foldcase(ip.*) && lo <= #char "z" && hi >= #char "a" {
435 if foldlo < #char "a" {
438 if foldhi > #char "z" {
441 if (foldlo <= foldhi) {
442 foldlo += #char "A" - #char "a";
443 foldhi += #char "A" - #char "a";
444 mark(*builder, foldlo, foldhi);
447 // If this Inst is not the last Inst in its list AND the next Inst is
448 // also a ByteRange AND the Insts have the same out, defer the merge.
449 if !get_last(ip.*) && get_opcode(inst[id + 1]) == .kInstByteRange && get_out(ip.*) == get_out(inst[id+1]) {
453 } else if get_opcode(ip.*) == .kInstEmptyWidth {
454 if ip.empty & (EmptyOp.kEmptyBeginLine|.kEmptyEndLine) && !marked_line_boundaries {
455 mark(*builder, #char "\n", #char "\n");
457 marked_line_boundaries = true;
459 if ip.empty & (EmptyOp.kEmptyWordBoundary|.kEmptyNonWordBoundary) && !marked_word_boundaries {
460 // We require two batches here: the first for ranges that are word
461 // characters, the second for ranges that are not word characters.
462 for isword: bool.[true, false] {
467 while j < 256 && is_word_char(cast(u8)i) == is_word_char(cast(u8)j) {
470 if is_word_char(cast(u8)i) == isword {
471 mark(*builder, cast(u8)i, cast(u8)(j - 1));
477 marked_word_boundaries = true;
482 bytemap, bytemap_range = build(*builder);
485// ByteMapBuilder implements a coloring algorithm.
487// The first phase is a series of "mark and merge" batches: we mark one or more
488// [lo-hi] ranges, then merge them into our internal state. Batching is not for
489// performance; rather, it means that the ranges are treated indistinguishably.
491// Internally, the ranges are represented using a bitmap that stores the splits
492// and a vector that stores the colors; both of them are indexed by the ranges'
493// last bytes. Thus, in order to merge a [lo-hi] range, we split at lo-1 and at
494// hi (if not already split), then recolor each range in between. The color map
495// (i.e. from the old color to the new color) is maintained for the lifetime of
496// the batch and so underpins this somewhat obscure approach to set operations.
498// The second phase builds the bytemap from our internal state: we recolor each
499// range, then store the new color (which is now the byte class) in each of the
500// corresponding array elements. Finally, we output the number of byte classes.
501ByteMapBuilder :: struct {
505 colormap: [..] Color_Pair;
512 Color_Pair :: struct {
518// Initial state: the [0-255] range has color 256.
519// This will avoid problems during the second phase,
520// in which we assign byte classes numbered from 0.
521init :: (using b: *ByteMapBuilder) {
522 init_bit_array(*splits, 256);
523 set_bit(*splits, 255);
528mark :: (using builder: *ByteMapBuilder, lo: s32, hi: s32) {
531 // Ignore any [0-255] ranges. They cause us to recolor every range, which
532 // has no effect on the eventual result and is therefore a waste of time.
533 if lo == 0 && hi == 255 return;
538 array_add(*ranges, r);
541find_next_set_bit :: (using set: Bit_Array, start: int) -> s16 {
543 // Mask out everything below start
544 word := slots[i] & (0xFFFF_FFFF_FFFF_FFFF << (start & 63));
546 return cast(u8)((i << 6) + bit_scan_forward(word) - 1);
550 while i < slots.count {
552 return cast(u8)((i << 6) + bit_scan_forward(slots[i]) - 1);
559merge :: (using builder: *ByteMapBuilder) {
561 lo := (cast(s16)r.lo) - 1;
564 if lo >= 0 && !splits[lo] {
565 set_bit(*splits, lo);
566 next := find_next_set_bit(splits, lo + 1);
567 colors[lo] = colors[next];
571 set_bit(*splits, hi);
572 next := find_next_set_bit(splits, hi + 1);
573 colors[hi] = colors[next];
578 next := find_next_set_bit(splits, c);
579 colors[next] = recolor(builder, colors[next]);
589build :: (using builder: *ByteMapBuilder) -> [256] u8, bytemap_range: int {
590 // Assign byte classes numbered from 0.
596 next := find_next_set_bit(splits, c);
597 b := cast(u8) recolor(builder, colors[next]);
604 return bytemap, nextcolor;
607recolor :: (using builder: *ByteMapBuilder, oldcolor: int) -> int {
608 // Yes, this is a linear search. There can be at most 256
609 // colors and there will typically be far fewer than that.
610 // Also, we need to consider keys *and* values in order to
611 // avoid recoloring a given range more than once per batch.
613 if it.a == oldcolor || it.b == oldcolor return it.b;
616 newcolor := nextcolor;
621 array_add(*colormap, p);
626// Prog::Flatten() implements a graph rewriting algorithm.
628// The overall process is similar to epsilon removal, but retains some epsilon
629// transitions: those from Capture and EmptyWidth instructions; and those from
630// nullable subexpressions. (The latter avoids quadratic blowup in transitions
631// in the worst case.) It might be best thought of as Alt instruction elision.
633// In conceptual terms, it divides the Prog into "trees" of instructions, then
634// traverses the "trees" in order to produce "lists" of instructions. A "tree"
635// is one or more instructions that grow from one "root" instruction to one or
636// more "leaf" instructions; if a "tree" has exactly one instruction, then the
637// "root" is also the "leaf". In most cases, a "root" is the successor of some
638// "leaf" (i.e. the "leaf" instruction's out() returns the "root" instruction)
639// and is considered a "successor root". A "leaf" can be a ByteRange, Capture,
640// EmptyWidth or Match instruction. However, this is insufficient for handling
641// nested nullable subexpressions correctly, so in some cases, a "root" is the
642// dominator of the instructions reachable from some "successor root" (i.e. it
643// has an unreachable predecessor) and is considered a "dominator root". Since
644// only Alt instructions can be "dominator roots" (other instructions would be
645// "leaves"), only Alt instructions are required to be marked as predecessors.
647// Dividing the Prog into "trees" comprises two passes: marking the "successor
648// roots" and the predecessors; and marking the "dominator roots". Sorting the
649// "successor roots" by their bytecode offsets enables iteration in order from
650// greatest to least during the second pass; by working backwards in this case
651// and flooding the graph no further than "leaves" and already marked "roots",
652// it becomes possible to mark "dominator roots" without doing excessive work.
654// Traversing the "trees" is just iterating over the "roots" in order of their
655// marking and flooding the graph no further than "leaves" and "roots". When a
656// "leaf" is reached, the instruction is copied with its successor remapped to
657// its "root" number. When a "root" is reached, a Nop instruction is generated
658// with its successor remapped similarly. As each "list" is produced, its last
659// instruction is marked as such. After all of the "lists" have been produced,
660// a pass over their instructions remaps their successors to bytecode offsets.
661flatten :: (using prog: *Prog) {
667 // Scratch structures. It's important that these are reused by functions
668 // that we call in loops because they would thrash the heap otherwise.
669 reachable: Sparse_Set;
671 init(*reachable, inst.count);
672 array_reserve(*stk, inst.count);
674 // First pass: Marks "successor roots" and predecessors.
675 // Builds the mapping from inst-ids to root-ids.
676 rootmap, predmap, preds_list := mark_successors(prog, *reachable, *stk);
678 // Second pass: Marks "dominator roots".
679 sorted := array_copy(rootmap.dense);
680 sorted.count = rootmap.count;
681 intro_sort(sorted, (a: $T, b: T) -> int {
682 if a.index < b.index {
690 if it.index != start_unanchored && it.index != start {
691 mark_dominator(prog, it.index, *rootmap, predmap, preds_list, *reachable, *stk);
695 // Third pass: Emits "lists". Remaps outs to root-ids.
696 // Builds the mapping from root-ids to flat-ids.
699 array_resize(*flatmap, rootmap.count, false);
700 array_reserve(*flat, inst.count);
703 flatmap[it.value] = flat.count;
704 emit_list(prog, it.index, *rootmap, *flat, *reachable, *stk);
705 set_last(*flat[flat.count - 1]);
706 // We have the bounds of the "list", so this is the
707 // most convenient point at which to compute hints.
708 compute_hints(prog, *flat, flatmap[it.value], flat.count);
711 list_count = flatmap.count;
712 memset(inst_count.data, 0, inst_count.count * size_of(type_of(inst_count[0])));
713 // Fourth pass: Remaps outs to flat-ids.
714 // Counts instructions by opcode.
716 if (get_opcode(it.*) != .kInstAltMatch) { // handled in EmitList()
717 set_out(it, flatmap[get_out(it.*)]);
719 inst_count[get_opcode(it.*)] += 1;
726 assert(total == flat.count);
728 // Remap start_unanchored and start.
729 if start_unanchored == 0 {
731 } else if (start_unanchored == start) {
732 start_unanchored = flatmap[1];
735 start_unanchored = flatmap[1];
739 // Finally, replace the old instructions with the new instructions.
742 // Populate the list heads for BitState.
743 // 512 instructions limits the memory footprint to 1KiB.
744 if inst.count <= 512 {
745 array_resize(*list_heads, inst.count, false);
746 // 0xFF makes it more obvious if we try to look up a non-head.
747 memset(list_heads.data, 0xFF, inst.count * size_of(type_of(list_heads[0])));
748 for 0..list_count - 1 {
749 list_heads[flatmap[it]] = cast(u16)it;
754mark_successors :: (using prog: *Prog, reachable: *Sparse_Set, stk: *[..] int) -> rootmap: Sparse_Array(int), predmap: Sparse_Array(int), preds_list: [..] [..] int {
755 get_or_add_preds :: (preds_list: *[..] [..] int, predmap: *Sparse_Array(int), id: int) -> *[..] int {
757 if !contains(predmap.*, id) {
758 add_unchecked(predmap, id, preds_list.count);
759 array_reserve(preds_list, preds_list.count + 1);
760 memset(preds_list.data + preds_list.count, 0, size_of(type_of(preds_list[0])));
761 preds_list.count += 1;
762 preds = *(preds_list.*)[preds_list.count - 1];
766 preds = *(preds_list.*)[get(predmap.*, id)];
771 rootmap: Sparse_Array(int);
772 predmap: Sparse_Array(int);
773 init(*rootmap, inst.count);
774 init(*predmap, inst.count);
775 preds_list: [..] [..] int;
777 // Mark the kInstFail instruction.
778 add_unchecked(*rootmap, 0, rootmap.count);
780 // Mark the start_unanchored and start instructions.
781 if !contains(rootmap, start_unanchored) {
782 add_unchecked(*rootmap, start_unanchored, rootmap.count);
784 if !contains(rootmap, start) {
785 add_unchecked(*rootmap, start, rootmap.count);
790 array_add(stk, start_unanchored);
793 if contains(reachable.*, id) continue;
794 add_unchecked(reachable, id);
797 if get_opcode(i) == {
798 case .kInstAltMatch; #through;
800 // Mark this instruction as a predecessor of each out.
801 preds := get_or_add_preds(*preds_list, *predmap, get_out(i));
802 array_add(preds, id);
803 preds = get_or_add_preds(*preds_list, *predmap, i.out1);
804 array_add(preds, id);
805 array_add(stk, i.out1);
806 array_add(stk, get_out(i));
808 case .kInstByteRange; #through;
809 case .kInstCapture; #through;
810 case .kInstEmptyWidth;
811 // Mark the out of this instruction as a "root".
812 if !contains(rootmap, get_out(i)) {
813 add_unchecked(*rootmap, get_out(i), rootmap.count);
815 array_add(stk, get_out(i));
818 array_add(stk, get_out(i));
820 case .kInstMatch; #through;
824 assert(false, "Unhandled opcode: %", get_opcode(i));
828 return rootmap, predmap, preds_list;
831mark_dominator :: (using prog: *Prog, root: int, rootmap: *Sparse_Array(int), predmap: Sparse_Array(int), preds_list: [..] [..] int, reachable: *Sparse_Set, stk: *[..] int) {
834 array_add(stk, root);
838 if contains(reachable.*, id) continue;
839 add_unchecked(reachable, id);
841 if id != root && contains(rootmap.*, id) {
842 // We reached another "tree" via epsilon transition.
847 if #complete get_opcode(i) == {
848 case .kInstAltMatch; #through;
850 array_add(stk, i.out1);
851 array_add(stk, get_out(i));
853 case .kInstByteRange; #through;
854 case .kInstCapture; #through;
855 case .kInstEmptyWidth;
858 array_add(stk, get_out(i));
860 case .kInstMatch; #through;
866 if contains(predmap, id) {
867 for pred: preds_list[get(predmap, id)] {
868 if !contains(reachable.*, pred) {
869 // id has a predecessor that cannot be reached from root!
870 // Therefore, id must be a "root" too - mark it as such.
871 if !contains(rootmap.*, id) {
872 add_unchecked(rootmap, id, rootmap.count);
880emit_list :: (using prog: *Prog, root: int, rootmap: *Sparse_Array(int), flat: *[..] Inst, reachable: *Sparse_Set, stk: *[..] int) {
883 array_add(stk, root);
886 if contains(reachable.*, id) continue;
887 add_unchecked(reachable, id);
889 if id != root && contains(rootmap.*, id) {
890 // We reached another "tree" via epsilon transition. Emit a kInstNop
891 // instruction so that the Prog does not become quadratically larger.
893 set_opcode(*i, .kInstNop);
894 set_out(*i, get(rootmap.*, id));
900 if get_opcode(i) == {
903 set_opcode(*newi, .kInstAltMatch);
904 set_out(*newi, flat.count + 1);
905 newi.out1 = cast(u32)flat.count + 2;
906 array_add(flat, newi);
907 array_add(stk, i.out1);
908 array_add(stk, get_out(i));
911 array_add(stk, i.out1);
912 array_add(stk, get_out(i));
914 case .kInstByteRange; #through;
915 case .kInstCapture; #through;
916 case .kInstEmptyWidth;
918 set_out(*newi, get(rootmap.*, get_out(i)));
919 array_add(flat, newi);
922 array_add(stk, get_out(i));
924 case .kInstMatch; #through;
929 assert(false, "Unhandled opcode: %", get_opcode(i));
934// For each ByteRange instruction in [begin, end), computes a hint to execution
935// engines: the delta to the next instruction (in flat) worth exploring iff the
936// current instruction matched.
938// Implements a coloring algorithm related to ByteMapBuilder, but in this case,
939// colors are instructions and recoloring ranges precisely identifies conflicts
940// between instructions. Iterating backwards over [begin, end) is guaranteed to
941// identify the nearest conflict (if any) with only linear complexity.
942compute_hints :: (using prog: *Prog, flat: *[..] Inst, begin: int, end: int) {
943 recolor :: (splits: *Bit_Array, colors: *[256] int, lo_in: int, hi: int, first: *int, id: int) {
944 // Like ByteMapBuilder, we split at lo-1 and at hi.
947 if lo >= 0 && !(splits.*)[lo] {
949 next := find_next_set_bit(splits.*, lo + 1);
950 (colors.*)[lo] = (colors.*)[next];
954 next := find_next_set_bit(splits.*, hi + 1);
955 (colors.*)[hi] = (colors.*)[next];
960 next := find_next_set_bit(splits.*, c);
961 // Ratchet backwards...
962 (first.*) = min(first.*, (colors.*)[next]);
963 // Recolor with id - because it's the new nearest conflict!
964 (colors.*)[next] = id;
971 init_bit_array(*splits, 256);
975 for < id: begin..end {
976 if (id == end || get_opcode((flat.*)[id]) != .kInstByteRange) {
979 clear_all_bits(*splits);
981 set_bit(*splits, 255);
983 // At this point, the [0-255] range is colored with id.
984 // Thus, hints cannot point beyond id; and if id == end,
985 // hints that would have pointed to id will be 0 instead.
990 // We recolor the [lo-hi] range with id. Note that first ratchets backwards
991 // from end to the nearest conflict (if any) during recoloring.
997 recolor(*splits, *colors, lo, hi, *first, id);
998 if get_foldcase(ip.*) && lo <= #char "z" && hi >= #char "a" {
1001 if (foldlo < #char "a") {
1004 if (foldhi > #char "z") {
1007 if (foldlo <= foldhi) {
1008 foldlo += #char "A" - #char "a";
1009 foldhi += #char "A" - #char "a";
1010 recolor(*splits, *colors, foldlo, foldhi, *first, id);
1015 hint := cast(u16) min(first - id, 32767);
1016 ip.hint_foldcase |= hint << 1;
1021// Accelerates to the first likely occurrence of the prefix.
1022// Returns position of the first byte or -1 if not found.
1023prefix_accel :: (using prog: *Prog, text: string, pos: int) -> int {
1024 assert(prefix_size >= 1);
1025 if prefix_size == 1 {
1026 result, found := index_of_char_2(text, cast(u8)prefix_front, pos);
1027 if !found return -1;
1031 return prefix_accel_front_and_back(prog, text, pos);
1035prefix_accel_front_and_back :: (using prog: *Prog, text: string, pos: int) -> int {
1036 assert(prefix_size >= 2);
1037 if text.count - pos < prefix_size {
1041 // Don't bother searching the last prefix_size_-1 bytes for prefix_front_.
1042 // This also means that probing for prefix_back_ doesn't go out of bounds.
1043 size := text.count - prefix_size + 1;
1045 // @ToDo Port AVX2 fast path once jai has intrinsics/assembler
1047 for i: pos..size-1 {
1048 if text[i] == prefix_front && text[i + prefix_size - 1] == prefix_back {
1056append :: (builder: *String_Builder, inst: Inst) {
1057 if get_opcode(inst) == {
1059 print_to_builder(builder, "opcode %", get_opcode(inst));
1062 print_to_builder(builder, "alt -> % | %", get_out(inst), inst.out1);
1064 case .kInstAltMatch;
1065 print_to_builder(builder, "altmatch -> % | %", get_out(inst), inst.out1);
1067 case .kInstByteRange;
1068 append(builder, "byte");
1069 if get_foldcase(inst) {
1070 append(builder, "/i");
1072 print_to_builder(builder, " [%-%] % -> %", format_byte(inst.lo), format_byte(inst.hi), get_hint(inst), get_out(inst));
1075 print_to_builder(builder, "capture % -> %", inst.cap, get_out(inst));
1077 case .kInstEmptyWidth;
1078 print_to_builder(builder, "emptywidth % -> %", inst.empty, get_out(inst));
1081 print_to_builder(builder, "match! %", inst.match_id);
1084 print_to_builder(builder, "nop -> %", get_out(inst));
1087 append(builder, "fail");
1091format_byte :: #bake_arguments formatInt(base = 16, minimum_digits = 2);
1093to_string :: (using prog: Prog) -> string {
1095 return to_string_flattened(prog);
1097 return to_string_unflattened(prog);
1101to_string_flattened :: (using prog: Prog) -> string {
1102 builder: String_Builder;
1103 defer free_buffers(*builder);
1106 print_to_builder(*builder, "%. ", it_index);
1108 print_to_builder(*builder, "%1+ ", it_index);
1110 append(*builder, it);
1111 append(*builder, #char "\n");
1113 return builder_to_string(*builder);
1116to_string_unflattened :: (using prog: Prog) -> string {
1117 builder: String_Builder;
1118 defer free_buffers(*builder);
1121 init(*q, prog.inst.count);
1122 add_if_nonzero(*q, start);
1125 print_to_builder(*builder, "%. ", it);
1126 append(*builder, i);
1127 append(*builder, #char "\n");
1128 add_if_nonzero(*q, get_out(i));
1129 if get_opcode(i) == .kInstAlt || get_opcode(i) == .kInstAltMatch {
1130 add_if_nonzero(*q, i.out1);
1133 return builder_to_string(*builder);
1140#import "Bit_Operations";
1142// @ToDo: Use assembler replacement for memchr instead, once jai supports it
1143index_of_char_2 :: (str: string, char: u8, start := 0) -> int, found:bool {
1144 for start..str.count-1 {
1145 if str[it] == char return it, true;