Coverage Report

Created: 2022-05-20 06:17

/src/serenity/Userland/Libraries/LibRegex/RegexOptimizer.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
3
 *
4
 * SPDX-License-Identifier: BSD-2-Clause
5
 */
6
7
#include <AK/QuickSort.h>
8
#include <AK/RedBlackTree.h>
9
#include <AK/Stack.h>
10
#include <LibRegex/Regex.h>
11
#include <LibRegex/RegexBytecodeStreamOptimizer.h>
12
#if REGEX_DEBUG
13
#    include <AK/ScopeGuard.h>
14
#    include <AK/ScopeLogger.h>
15
#endif
16
17
namespace regex {
18
19
using Detail::Block;
20
21
template<typename Parser>
22
void Regex<Parser>::run_optimization_passes()
23
4.20k
{
24
4.20k
    parser_result.bytecode.flatten();
25
26
    // Rewrite fork loops as atomic groups
27
    // e.g. a*b -> (ATOMIC a*)b
28
4.20k
    attempt_rewrite_loops_as_atomic_groups(split_basic_blocks(parser_result.bytecode));
29
30
4.20k
    parser_result.bytecode.flatten();
31
4.20k
}
regex::Regex<regex::PosixBasicParser>::run_optimization_passes()
Line
Count
Source
23
4.20k
{
24
4.20k
    parser_result.bytecode.flatten();
25
26
    // Rewrite fork loops as atomic groups
27
    // e.g. a*b -> (ATOMIC a*)b
28
4.20k
    attempt_rewrite_loops_as_atomic_groups(split_basic_blocks(parser_result.bytecode));
29
30
4.20k
    parser_result.bytecode.flatten();
31
4.20k
}
Unexecuted instantiation: regex::Regex<regex::PosixExtendedParser>::run_optimization_passes()
Unexecuted instantiation: regex::Regex<regex::ECMA262Parser>::run_optimization_passes()
32
33
template<typename Parser>
34
typename Regex<Parser>::BasicBlockList Regex<Parser>::split_basic_blocks(ByteCode const& bytecode)
35
4.20k
{
36
4.20k
    BasicBlockList block_boundaries;
37
4.20k
    size_t end_of_last_block = 0;
38
39
4.20k
    auto bytecode_size = bytecode.size();
40
41
4.20k
    MatchState state;
42
4.20k
    state.instruction_position = 0;
43
70.1M
    auto check_jump = [&]<typename T>(OpCode const& opcode) {
44
70.1M
        auto& op = static_cast<T const&>(opcode);
45
70.1M
        ssize_t jump_offset = op.size() + op.offset();
46
70.1M
        if (jump_offset >= 0) {
47
38.7M
            block_boundaries.append({ end_of_last_block, state.instruction_position });
48
38.7M
            end_of_last_block = state.instruction_position + opcode.size();
49
38.7M
        } else {
50
            // This op jumps back, see if that's within this "block".
51
31.3M
            if (jump_offset + state.instruction_position > end_of_last_block) {
52
                // Split the block!
53
2.45M
                block_boundaries.append({ end_of_last_block, jump_offset + state.instruction_position });
54
2.45M
                block_boundaries.append({ jump_offset + state.instruction_position, state.instruction_position });
55
2.45M
                end_of_last_block = state.instruction_position + opcode.size();
56
28.9M
            } else {
57
                // Nope, it's just a jump to another block
58
28.9M
                block_boundaries.append({ end_of_last_block, state.instruction_position });
59
28.9M
                end_of_last_block = state.instruction_position + opcode.size();
60
28.9M
            }
61
31.3M
        }
62
70.1M
    };
Unexecuted instantiation: _ZZN5regex5RegexINS_16PosixBasicParserEE18split_basic_blocksERKNS_8ByteCodeEENKUlTyRKNS_6OpCodeEE_clINS_11OpCode_JumpEEEDaS8_
_ZZN5regex5RegexINS_16PosixBasicParserEE18split_basic_blocksERKNS_8ByteCodeEENKUlTyRKNS_6OpCodeEE_clINS_19OpCode_JumpNonEmptyEEEDaS8_
Line
Count
Source
43
31.3M
    auto check_jump = [&]<typename T>(OpCode const& opcode) {
44
31.3M
        auto& op = static_cast<T const&>(opcode);
45
31.3M
        ssize_t jump_offset = op.size() + op.offset();
46
31.3M
        if (jump_offset >= 0) {
47
0
            block_boundaries.append({ end_of_last_block, state.instruction_position });
48
0
            end_of_last_block = state.instruction_position + opcode.size();
49
31.3M
        } else {
50
            // This op jumps back, see if that's within this "block".
51
31.3M
            if (jump_offset + state.instruction_position > end_of_last_block) {
52
                // Split the block!
53
2.45M
                block_boundaries.append({ end_of_last_block, jump_offset + state.instruction_position });
54
2.45M
                block_boundaries.append({ jump_offset + state.instruction_position, state.instruction_position });
55
2.45M
                end_of_last_block = state.instruction_position + opcode.size();
56
28.9M
            } else {
57
                // Nope, it's just a jump to another block
58
28.9M
                block_boundaries.append({ end_of_last_block, state.instruction_position });
59
28.9M
                end_of_last_block = state.instruction_position + opcode.size();
60
28.9M
            }
61
31.3M
        }
62
31.3M
    };
Unexecuted instantiation: _ZZN5regex5RegexINS_16PosixBasicParserEE18split_basic_blocksERKNS_8ByteCodeEENKUlTyRKNS_6OpCodeEE_clINS_15OpCode_ForkJumpEEEDaS8_
_ZZN5regex5RegexINS_16PosixBasicParserEE18split_basic_blocksERKNS_8ByteCodeEENKUlTyRKNS_6OpCodeEE_clINS_15OpCode_ForkStayEEEDaS8_
Line
Count
Source
43
38.7M
    auto check_jump = [&]<typename T>(OpCode const& opcode) {
44
38.7M
        auto& op = static_cast<T const&>(opcode);
45
38.7M
        ssize_t jump_offset = op.size() + op.offset();
46
38.7M
        if (jump_offset >= 0) {
47
38.7M
            block_boundaries.append({ end_of_last_block, state.instruction_position });
48
38.7M
            end_of_last_block = state.instruction_position + opcode.size();
49
38.7M
        } else {
50
            // This op jumps back, see if that's within this "block".
51
0
            if (jump_offset + state.instruction_position > end_of_last_block) {
52
                // Split the block!
53
0
                block_boundaries.append({ end_of_last_block, jump_offset + state.instruction_position });
54
0
                block_boundaries.append({ jump_offset + state.instruction_position, state.instruction_position });
55
0
                end_of_last_block = state.instruction_position + opcode.size();
56
0
            } else {
57
                // Nope, it's just a jump to another block
58
0
                block_boundaries.append({ end_of_last_block, state.instruction_position });
59
0
                end_of_last_block = state.instruction_position + opcode.size();
60
0
            }
61
0
        }
62
38.7M
    };
Unexecuted instantiation: _ZZN5regex5RegexINS_19PosixExtendedParserEE18split_basic_blocksERKNS_8ByteCodeEENKUlTyRKNS_6OpCodeEE_clINS_11OpCode_JumpEEEDaS8_
Unexecuted instantiation: _ZZN5regex5RegexINS_19PosixExtendedParserEE18split_basic_blocksERKNS_8ByteCodeEENKUlTyRKNS_6OpCodeEE_clINS_19OpCode_JumpNonEmptyEEEDaS8_
Unexecuted instantiation: _ZZN5regex5RegexINS_19PosixExtendedParserEE18split_basic_blocksERKNS_8ByteCodeEENKUlTyRKNS_6OpCodeEE_clINS_15OpCode_ForkJumpEEEDaS8_
Unexecuted instantiation: _ZZN5regex5RegexINS_19PosixExtendedParserEE18split_basic_blocksERKNS_8ByteCodeEENKUlTyRKNS_6OpCodeEE_clINS_15OpCode_ForkStayEEEDaS8_
Unexecuted instantiation: _ZZN5regex5RegexINS_13ECMA262ParserEE18split_basic_blocksERKNS_8ByteCodeEENKUlTyRKNS_6OpCodeEE_clINS_11OpCode_JumpEEEDaS8_
Unexecuted instantiation: _ZZN5regex5RegexINS_13ECMA262ParserEE18split_basic_blocksERKNS_8ByteCodeEENKUlTyRKNS_6OpCodeEE_clINS_19OpCode_JumpNonEmptyEEEDaS8_
Unexecuted instantiation: _ZZN5regex5RegexINS_13ECMA262ParserEE18split_basic_blocksERKNS_8ByteCodeEENKUlTyRKNS_6OpCodeEE_clINS_15OpCode_ForkJumpEEEDaS8_
Unexecuted instantiation: _ZZN5regex5RegexINS_13ECMA262ParserEE18split_basic_blocksERKNS_8ByteCodeEENKUlTyRKNS_6OpCodeEE_clINS_15OpCode_ForkStayEEEDaS8_
63
348M
    for (;;) {
64
348M
        auto& opcode = bytecode.get_opcode(state);
65
66
348M
        switch (opcode.opcode_id()) {
67
0
        case OpCodeId::Jump:
68
0
            check_jump.template operator()<OpCode_Jump>(opcode);
69
0
            break;
70
31.3M
        case OpCodeId::JumpNonEmpty:
71
31.3M
            check_jump.template operator()<OpCode_JumpNonEmpty>(opcode);
72
31.3M
            break;
73
0
        case OpCodeId::ForkJump:
74
0
            check_jump.template operator()<OpCode_ForkJump>(opcode);
75
0
            break;
76
38.7M
        case OpCodeId::ForkStay:
77
38.7M
            check_jump.template operator()<OpCode_ForkStay>(opcode);
78
38.7M
            break;
79
0
        case OpCodeId::FailForks:
80
0
            block_boundaries.append({ end_of_last_block, state.instruction_position });
81
0
            end_of_last_block = state.instruction_position + opcode.size();
82
0
            break;
83
11.2M
        case OpCodeId::Repeat: {
84
            // Repeat produces two blocks, one containing its repeated expr, and one after that.
85
11.2M
            auto repeat_start = state.instruction_position - static_cast<OpCode_Repeat const&>(opcode).offset();
86
11.2M
            if (repeat_start > end_of_last_block)
87
1.86M
                block_boundaries.append({ end_of_last_block, repeat_start });
88
11.2M
            block_boundaries.append({ repeat_start, state.instruction_position });
89
11.2M
            end_of_last_block = state.instruction_position + opcode.size();
90
11.2M
            break;
91
0
        }
92
266M
        default:
93
266M
            break;
94
348M
        }
95
96
348M
        auto next_ip = state.instruction_position + opcode.size();
97
348M
        if (next_ip < bytecode_size)
98
348M
            state.instruction_position = next_ip;
99
4.20k
        else
100
4.20k
            break;
101
348M
    }
102
103
4.20k
    if (end_of_last_block < bytecode_size)
104
2.63k
        block_boundaries.append({ end_of_last_block, bytecode_size });
105
106
1.33G
    quick_sort(block_boundaries, [](auto& a, auto& b) { return a.start < b.start; });
auto regex::Regex<regex::PosixBasicParser>::split_basic_blocks(regex::ByteCode const&)::{lambda(auto:1&, auto:2&)#1}::operator()<regex::Detail::Block, regex::Detail>(regex::Detail::Block&, regex::Detail&) const
Line
Count
Source
106
1.33G
    quick_sort(block_boundaries, [](auto& a, auto& b) { return a.start < b.start; });
Unexecuted instantiation: auto regex::Regex<regex::PosixExtendedParser>::split_basic_blocks(regex::ByteCode const&)::{lambda(auto:1&, auto:2&)#1}::operator()<regex::Detail::Block, regex::Detail>(regex::Detail::Block&, regex::Detail&) const
Unexecuted instantiation: auto regex::Regex<regex::ECMA262Parser>::split_basic_blocks(regex::ByteCode const&)::{lambda(auto:1&, auto:2&)#1}::operator()<regex::Detail::Block, regex::Detail>(regex::Detail::Block&, regex::Detail&) const
107
108
4.20k
    return block_boundaries;
109
4.20k
}
regex::Regex<regex::PosixBasicParser>::split_basic_blocks(regex::ByteCode const&)
Line
Count
Source
35
4.20k
{
36
4.20k
    BasicBlockList block_boundaries;
37
4.20k
    size_t end_of_last_block = 0;
38
39
4.20k
    auto bytecode_size = bytecode.size();
40
41
4.20k
    MatchState state;
42
4.20k
    state.instruction_position = 0;
43
4.20k
    auto check_jump = [&]<typename T>(OpCode const& opcode) {
44
4.20k
        auto& op = static_cast<T const&>(opcode);
45
4.20k
        ssize_t jump_offset = op.size() + op.offset();
46
4.20k
        if (jump_offset >= 0) {
47
4.20k
            block_boundaries.append({ end_of_last_block, state.instruction_position });
48
4.20k
            end_of_last_block = state.instruction_position + opcode.size();
49
4.20k
        } else {
50
            // This op jumps back, see if that's within this "block".
51
4.20k
            if (jump_offset + state.instruction_position > end_of_last_block) {
52
                // Split the block!
53
4.20k
                block_boundaries.append({ end_of_last_block, jump_offset + state.instruction_position });
54
4.20k
                block_boundaries.append({ jump_offset + state.instruction_position, state.instruction_position });
55
4.20k
                end_of_last_block = state.instruction_position + opcode.size();
56
4.20k
            } else {
57
                // Nope, it's just a jump to another block
58
4.20k
                block_boundaries.append({ end_of_last_block, state.instruction_position });
59
4.20k
                end_of_last_block = state.instruction_position + opcode.size();
60
4.20k
            }
61
4.20k
        }
62
4.20k
    };
63
348M
    for (;;) {
64
348M
        auto& opcode = bytecode.get_opcode(state);
65
66
348M
        switch (opcode.opcode_id()) {
67
0
        case OpCodeId::Jump:
68
0
            check_jump.template operator()<OpCode_Jump>(opcode);
69
0
            break;
70
31.3M
        case OpCodeId::JumpNonEmpty:
71
31.3M
            check_jump.template operator()<OpCode_JumpNonEmpty>(opcode);
72
31.3M
            break;
73
0
        case OpCodeId::ForkJump:
74
0
            check_jump.template operator()<OpCode_ForkJump>(opcode);
75
0
            break;
76
38.7M
        case OpCodeId::ForkStay:
77
38.7M
            check_jump.template operator()<OpCode_ForkStay>(opcode);
78
38.7M
            break;
79
0
        case OpCodeId::FailForks:
80
0
            block_boundaries.append({ end_of_last_block, state.instruction_position });
81
0
            end_of_last_block = state.instruction_position + opcode.size();
82
0
            break;
83
11.2M
        case OpCodeId::Repeat: {
84
            // Repeat produces two blocks, one containing its repeated expr, and one after that.
85
11.2M
            auto repeat_start = state.instruction_position - static_cast<OpCode_Repeat const&>(opcode).offset();
86
11.2M
            if (repeat_start > end_of_last_block)
87
1.86M
                block_boundaries.append({ end_of_last_block, repeat_start });
88
11.2M
            block_boundaries.append({ repeat_start, state.instruction_position });
89
11.2M
            end_of_last_block = state.instruction_position + opcode.size();
90
11.2M
            break;
91
0
        }
92
266M
        default:
93
266M
            break;
94
348M
        }
95
96
348M
        auto next_ip = state.instruction_position + opcode.size();
97
348M
        if (next_ip < bytecode_size)
98
348M
            state.instruction_position = next_ip;
99
4.20k
        else
100
4.20k
            break;
101
348M
    }
102
103
4.20k
    if (end_of_last_block < bytecode_size)
104
2.63k
        block_boundaries.append({ end_of_last_block, bytecode_size });
105
106
4.20k
    quick_sort(block_boundaries, [](auto& a, auto& b) { return a.start < b.start; });
107
108
4.20k
    return block_boundaries;
109
4.20k
}
Unexecuted instantiation: regex::Regex<regex::PosixExtendedParser>::split_basic_blocks(regex::ByteCode const&)
Unexecuted instantiation: regex::Regex<regex::ECMA262Parser>::split_basic_blocks(regex::ByteCode const&)
110
111
enum class AtomicRewritePreconditionResult {
112
    SatisfiedWithProperHeader,
113
    SatisfiedWithEmptyHeader,
114
    NotSatisfied,
115
};
116
static AtomicRewritePreconditionResult block_satisfies_atomic_rewrite_precondition(ByteCode const& bytecode, Block const& repeated_block, Block const& following_block)
117
1.76M
{
118
1.76M
    Vector<Vector<CompareTypeAndValuePair>> repeated_values;
119
1.76M
    HashTable<size_t> active_capture_groups;
120
1.76M
    MatchState state;
121
20.3M
    for (state.instruction_position = repeated_block.start; state.instruction_position < repeated_block.end;) {
122
18.6M
        auto& opcode = bytecode.get_opcode(state);
123
18.6M
        switch (opcode.opcode_id()) {
124
16.2M
        case OpCodeId::Compare: {
125
16.2M
            auto compares = static_cast<OpCode_Compare const&>(opcode).flat_compares();
126
16.2M
            if (repeated_values.is_empty() && any_of(compares, [](auto& compare) { return compare.type == CharacterCompareType::AnyChar; }))
127
48.3k
                return AtomicRewritePreconditionResult::NotSatisfied;
128
16.1M
            repeated_values.append(move(compares));
129
16.1M
            break;
130
16.2M
        }
131
0
        case OpCodeId::CheckBegin:
132
0
        case OpCodeId::CheckEnd:
133
0
            if (repeated_values.is_empty())
134
0
                return AtomicRewritePreconditionResult::SatisfiedWithProperHeader;
135
0
            break;
136
0
        case OpCodeId::CheckBoundary:
137
            // FIXME: What should we do with these? for now, let's fail.
138
0
            return AtomicRewritePreconditionResult::NotSatisfied;
139
0
        case OpCodeId::Restore:
140
0
        case OpCodeId::GoBack:
141
0
            return AtomicRewritePreconditionResult::NotSatisfied;
142
322k
        case OpCodeId::SaveRightCaptureGroup:
143
322k
            active_capture_groups.set(static_cast<OpCode_SaveRightCaptureGroup const&>(opcode).id());
144
322k
            break;
145
323k
        case OpCodeId::SaveLeftCaptureGroup:
146
323k
            active_capture_groups.set(static_cast<OpCode_SaveLeftCaptureGroup const&>(opcode).id());
147
323k
            break;
148
1.76M
        default:
149
1.76M
            break;
150
18.6M
        }
151
152
18.5M
        state.instruction_position += opcode.size();
153
18.5M
    }
154
1.71M
    dbgln_if(REGEX_DEBUG, "Found {} entries in reference", repeated_values.size());
155
1.71M
    dbgln_if(REGEX_DEBUG, "Found {} active capture groups", active_capture_groups.size());
156
157
1.71M
    bool following_block_has_at_least_one_compare = false;
158
    // Find the first compare in the following block, it must NOT match any of the values in `repeated_values'.
159
2.33M
    for (state.instruction_position = following_block.start; state.instruction_position < following_block.end;) {
160
2.33M
        auto& opcode = bytecode.get_opcode(state);
161
2.33M
        switch (opcode.opcode_id()) {
162
        // Note: These have to exist since we're effectively repeating the following block as well
163
499k
        case OpCodeId::SaveRightCaptureGroup:
164
499k
            active_capture_groups.set(static_cast<OpCode_SaveRightCaptureGroup const&>(opcode).id());
165
499k
            break;
166
68.7k
        case OpCodeId::SaveLeftCaptureGroup:
167
68.7k
            active_capture_groups.set(static_cast<OpCode_SaveLeftCaptureGroup const&>(opcode).id());
168
68.7k
            break;
169
1.71M
        case OpCodeId::Compare: {
170
1.71M
            following_block_has_at_least_one_compare = true;
171
            // We found a compare, let's see what it has.
172
1.71M
            auto compares = static_cast<OpCode_Compare const&>(opcode).flat_compares();
173
1.71M
            if (compares.is_empty())
174
0
                break;
175
176
4.34M
            if (any_of(compares, [&](auto& compare) {
177
4.34M
                    return compare.type == CharacterCompareType::AnyChar
178
4.34M
                        || (compare.type == CharacterCompareType::Reference && active_capture_groups.contains(compare.value));
179
4.34M
                }))
180
72.6k
                return AtomicRewritePreconditionResult::NotSatisfied;
181
182
10.6M
            for (auto& repeated_value : repeated_values) {
183
                // FIXME: This is too naive!
184
11.8M
                if (any_of(repeated_value, [](auto& compare) { return compare.type == CharacterCompareType::AnyChar; }))
185
761k
                    return AtomicRewritePreconditionResult::NotSatisfied;
186
187
10.9M
                for (auto& repeated_compare : repeated_value) {
188
                    // FIXME: This is too naive! it will miss _tons_ of cases since it doesn't check ranges!
189
16.5M
                    if (any_of(compares, [&](auto& compare) { return compare.type == repeated_compare.type && compare.value == repeated_compare.value; }))
190
884k
                        return AtomicRewritePreconditionResult::NotSatisfied;
191
10.9M
                }
192
9.86M
            }
193
564
            return AtomicRewritePreconditionResult::SatisfiedWithProperHeader;
194
1.64M
        }
195
0
        case OpCodeId::CheckBegin:
196
21
        case OpCodeId::CheckEnd:
197
21
            return AtomicRewritePreconditionResult::SatisfiedWithProperHeader; // Nothing can match the end!
198
0
        case OpCodeId::CheckBoundary:
199
            // FIXME: What should we do with these? For now, consider them a failure.
200
0
            return AtomicRewritePreconditionResult::NotSatisfied;
201
46.7k
        default:
202
46.7k
            break;
203
2.33M
        }
204
205
615k
        state.instruction_position += opcode.size();
206
615k
    }
207
208
321
    if (following_block_has_at_least_one_compare)
209
0
        return AtomicRewritePreconditionResult::SatisfiedWithProperHeader;
210
321
    return AtomicRewritePreconditionResult::SatisfiedWithEmptyHeader;
211
321
}
212
213
template<typename Parser>
214
void Regex<Parser>::attempt_rewrite_loops_as_atomic_groups(BasicBlockList const& basic_blocks)
215
4.20k
{
216
4.20k
    auto& bytecode = parser_result.bytecode;
217
4.20k
    if constexpr (REGEX_DEBUG) {
218
4.20k
        RegexDebug dbg;
219
4.20k
        dbg.print_bytecode(*this);
220
4.20k
        for (auto const& block : basic_blocks)
221
4.20k
            dbgln("block from {} to {}", block.start, block.end);
222
4.20k
    }
223
224
    // A pattern such as:
225
    //     bb0       |  RE0
226
    //               |  ForkX bb0
227
    //     -------------------------
228
    //     bb1       |  RE1
229
    // can be rewritten as:
230
    //     -------------------------
231
    //     bb0       | RE0
232
    //               | ForkReplaceX bb0
233
    //     -------------------------
234
    //     bb1       | RE1
235
    // provided that first(RE1) not-in end(RE0), which is to say
236
    // that RE1 cannot start with whatever RE0 has matched (ever).
237
    //
238
    // Alternatively, a second form of this pattern can also occur:
239
    //     bb0 | *
240
    //         | ForkX bb2
241
    //     ------------------------
242
    //     bb1 | RE0
243
    //         | Jump bb0
244
    //     ------------------------
245
    //     bb2 | RE1
246
    // which can be transformed (with the same preconditions) to:
247
    //     bb0 | *
248
    //         | ForkReplaceX bb2
249
    //     ------------------------
250
    //     bb1 | RE0
251
    //         | Jump bb0
252
    //     ------------------------
253
    //     bb2 | RE1
254
255
4.20k
    enum class AlternateForm {
256
4.20k
        DirectLoopWithoutHeader,               // loop without proper header, a block forking to itself. i.e. the first form.
257
4.20k
        DirectLoopWithoutHeaderAndEmptyFollow, // loop without proper header, a block forking to itself. i.e. the first form but with RE1 being empty.
258
4.20k
        DirectLoopWithHeader,                  // loop with proper header, i.e. the second form.
259
4.20k
    };
260
4.20k
    struct CandidateBlock {
261
4.20k
        Block forking_block;
262
4.20k
        Optional<Block> new_target_block;
263
4.20k
        AlternateForm form;
264
4.20k
    };
265
4.20k
    Vector<CandidateBlock> candidate_blocks;
266
267
46.4M
    auto is_an_eligible_jump = [](OpCode const& opcode, size_t ip, size_t block_start, AlternateForm alternate_form) {
268
46.4M
        switch (opcode.opcode_id()) {
269
15.8M
        case OpCodeId::JumpNonEmpty: {
270
15.8M
            auto const& op = static_cast<OpCode_JumpNonEmpty const&>(opcode);
271
15.8M
            auto form = op.form();
272
15.8M
            if (form != OpCodeId::Jump && alternate_form == AlternateForm::DirectLoopWithHeader)
273
249k
                return false;
274
15.6M
            if (form != OpCodeId::ForkJump && form != OpCodeId::ForkStay && alternate_form == AlternateForm::DirectLoopWithoutHeader)
275
7.68M
                return false;
276
7.93M
            return op.offset() + ip + opcode.size() == block_start;
277
15.6M
        }
278
0
        case OpCodeId::ForkJump:
279
0
            if (alternate_form == AlternateForm::DirectLoopWithHeader)
280
0
                return false;
281
0
            return static_cast<OpCode_ForkJump const&>(opcode).offset() + ip + opcode.size() == block_start;
282
22.3M
        case OpCodeId::ForkStay:
283
22.3M
            if (alternate_form == AlternateForm::DirectLoopWithHeader)
284
11.1M
                return false;
285
11.1M
            return static_cast<OpCode_ForkStay const&>(opcode).offset() + ip + opcode.size() == block_start;
286
0
        case OpCodeId::Jump:
287
            // Infinite loop does *not* produce forks.
288
0
            if (alternate_form == AlternateForm::DirectLoopWithoutHeader)
289
0
                return false;
290
0
            if (alternate_form == AlternateForm::DirectLoopWithHeader)
291
0
                return static_cast<OpCode_Jump const&>(opcode).offset() + ip + opcode.size() == block_start;
292
0
            VERIFY_NOT_REACHED();
293
8.32M
        default:
294
8.32M
            return false;
295
46.4M
        }
296
46.4M
    };
regex::Regex<regex::PosixBasicParser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)::{lambda(regex::OpCode const&, unsigned long, unsigned long, regex::Regex<regex::PosixBasicParser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)::AlternateForm)#1}::operator()(regex::OpCode const&, unsigned long, unsigned long, regex::Regex<regex::PosixBasicParser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)::AlternateForm) const
Line
Count
Source
267
46.4M
    auto is_an_eligible_jump = [](OpCode const& opcode, size_t ip, size_t block_start, AlternateForm alternate_form) {
268
46.4M
        switch (opcode.opcode_id()) {
269
15.8M
        case OpCodeId::JumpNonEmpty: {
270
15.8M
            auto const& op = static_cast<OpCode_JumpNonEmpty const&>(opcode);
271
15.8M
            auto form = op.form();
272
15.8M
            if (form != OpCodeId::Jump && alternate_form == AlternateForm::DirectLoopWithHeader)
273
249k
                return false;
274
15.6M
            if (form != OpCodeId::ForkJump && form != OpCodeId::ForkStay && alternate_form == AlternateForm::DirectLoopWithoutHeader)
275
7.68M
                return false;
276
7.93M
            return op.offset() + ip + opcode.size() == block_start;
277
15.6M
        }
278
0
        case OpCodeId::ForkJump:
279
0
            if (alternate_form == AlternateForm::DirectLoopWithHeader)
280
0
                return false;
281
0
            return static_cast<OpCode_ForkJump const&>(opcode).offset() + ip + opcode.size() == block_start;
282
22.3M
        case OpCodeId::ForkStay:
283
22.3M
            if (alternate_form == AlternateForm::DirectLoopWithHeader)
284
11.1M
                return false;
285
11.1M
            return static_cast<OpCode_ForkStay const&>(opcode).offset() + ip + opcode.size() == block_start;
286
0
        case OpCodeId::Jump:
287
            // Infinite loop does *not* produce forks.
288
0
            if (alternate_form == AlternateForm::DirectLoopWithoutHeader)
289
0
                return false;
290
0
            if (alternate_form == AlternateForm::DirectLoopWithHeader)
291
0
                return static_cast<OpCode_Jump const&>(opcode).offset() + ip + opcode.size() == block_start;
292
0
            VERIFY_NOT_REACHED();
293
8.32M
        default:
294
8.32M
            return false;
295
46.4M
        }
296
46.4M
    };
Unexecuted instantiation: regex::Regex<regex::PosixExtendedParser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)::{lambda(regex::OpCode const&, unsigned long, unsigned long, regex::Regex<regex::PosixExtendedParser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)::AlternateForm)#1}::operator()(regex::OpCode const&, unsigned long, unsigned long, regex::Regex<regex::PosixExtendedParser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)::AlternateForm) const
Unexecuted instantiation: regex::Regex<regex::ECMA262Parser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)::{lambda(regex::OpCode const&, unsigned long, unsigned long, regex::Regex<regex::ECMA262Parser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)::AlternateForm)#1}::operator()(regex::OpCode const&, unsigned long, unsigned long, regex::Regex<regex::ECMA262Parser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)::AlternateForm) const
297
23.2M
    for (size_t i = 0; i < basic_blocks.size(); ++i) {
298
23.2M
        auto forking_block = basic_blocks[i];
299
23.2M
        Optional<Block> fork_fallback_block;
300
23.2M
        if (i + 1 < basic_blocks.size())
301
23.2M
            fork_fallback_block = basic_blocks[i + 1];
302
23.2M
        MatchState state;
303
        // Check if the last instruction in this block is a jump to the block itself:
304
23.2M
        {
305
23.2M
            state.instruction_position = forking_block.end;
306
23.2M
            auto& opcode = bytecode.get_opcode(state);
307
23.2M
            if (is_an_eligible_jump(opcode, state.instruction_position, forking_block.start, AlternateForm::DirectLoopWithoutHeader)) {
308
                // We've found RE0 (and RE1 is just the following block, if any), let's see if the precondition applies.
309
                // if RE1 is empty, there's no first(RE1), so this is an automatic pass.
310
190k
                if (!fork_fallback_block.has_value() || fork_fallback_block->end == fork_fallback_block->start) {
311
114
                    candidate_blocks.append({ forking_block, fork_fallback_block, AlternateForm::DirectLoopWithoutHeader });
312
114
                    break;
313
114
                }
314
315
190k
                auto precondition = block_satisfies_atomic_rewrite_precondition(bytecode, forking_block, *fork_fallback_block);
316
190k
                if (precondition == AtomicRewritePreconditionResult::SatisfiedWithProperHeader) {
317
221
                    candidate_blocks.append({ forking_block, fork_fallback_block, AlternateForm::DirectLoopWithoutHeader });
318
221
                    break;
319
221
                }
320
190k
                if (precondition == AtomicRewritePreconditionResult::SatisfiedWithEmptyHeader) {
321
64
                    candidate_blocks.append({ forking_block, fork_fallback_block, AlternateForm::DirectLoopWithoutHeaderAndEmptyFollow });
322
64
                    break;
323
64
                }
324
190k
            }
325
23.2M
        }
326
        // Check if the last instruction in the last block is a direct jump to this block
327
23.2M
        if (fork_fallback_block.has_value()) {
328
23.2M
            state.instruction_position = fork_fallback_block->end;
329
23.2M
            auto& opcode = bytecode.get_opcode(state);
330
23.2M
            if (is_an_eligible_jump(opcode, state.instruction_position, forking_block.start, AlternateForm::DirectLoopWithHeader)) {
331
                // We've found bb1 and bb0, let's just make sure that bb0 forks to bb2.
332
1.57M
                state.instruction_position = forking_block.end;
333
1.57M
                auto& opcode = bytecode.get_opcode(state);
334
1.57M
                if (opcode.opcode_id() == OpCodeId::ForkJump || opcode.opcode_id() == OpCodeId::ForkStay) {
335
1.57M
                    Optional<Block> block_following_fork_fallback;
336
1.57M
                    if (i + 2 < basic_blocks.size())
337
1.57M
                        block_following_fork_fallback = basic_blocks[i + 2];
338
1.57M
                    if (!block_following_fork_fallback.has_value()
339
1.57M
                        || block_satisfies_atomic_rewrite_precondition(bytecode, *fork_fallback_block, *block_following_fork_fallback) != AtomicRewritePreconditionResult::NotSatisfied) {
340
673
                        candidate_blocks.append({ forking_block, {}, AlternateForm::DirectLoopWithHeader });
341
673
                        break;
342
673
                    }
343
1.57M
                }
344
1.57M
            }
345
23.2M
        }
346
23.2M
    }
347
348
4.20k
    dbgln_if(REGEX_DEBUG, "Found {} candidate blocks", candidate_blocks.size());
349
4.20k
    if (candidate_blocks.is_empty()) {
350
3.13k
        dbgln_if(REGEX_DEBUG, "Failed to find anything for {}", pattern_value);
351
3.13k
        return;
352
3.13k
    }
353
354
1.07k
    RedBlackTree<size_t, size_t> needed_patches;
355
356
    // Reverse the blocks, so we can patch the bytecode without messing with the latter patches.
357
1.07k
    quick_sort(candidate_blocks, [](auto& a, auto& b) { return b.forking_block.start > a.forking_block.start; });
Unexecuted instantiation: auto regex::Regex<regex::PosixBasicParser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)::{lambda(auto:1&, auto:2&)#1}::operator()<regex::Regex<regex::PosixBasicParser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)::CandidateBlock, {lambda(auto:1&, auto:2&)#1}::operator()>(regex::Regex<regex::PosixBasicParser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)::CandidateBlock&, {lambda(auto:1&, auto:2&)#1}::operator()&) const
Unexecuted instantiation: auto regex::Regex<regex::PosixExtendedParser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)::{lambda(auto:1&, auto:2&)#1}::operator()<regex::Regex<regex::PosixExtendedParser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)::CandidateBlock, {lambda(auto:1&, auto:2&)#1}::operator()>(regex::Regex<regex::PosixExtendedParser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)::CandidateBlock&, {lambda(auto:1&, auto:2&)#1}::operator()&) const
Unexecuted instantiation: auto regex::Regex<regex::ECMA262Parser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)::{lambda(auto:1&, auto:2&)#1}::operator()<regex::Regex<regex::ECMA262Parser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)::CandidateBlock, {lambda(auto:1&, auto:2&)#1}::operator()>(regex::Regex<regex::ECMA262Parser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)::CandidateBlock&, {lambda(auto:1&, auto:2&)#1}::operator()&) const
358
1.07k
    for (auto& candidate : candidate_blocks) {
359
        // Note that both forms share a ForkReplace patch in forking_block.
360
        // Patch the ForkX in forking_block to be a ForkReplaceX instead.
361
1.07k
        auto& opcode_id = bytecode[candidate.forking_block.end];
362
1.07k
        if (opcode_id == (ByteCodeValueType)OpCodeId::ForkStay) {
363
673
            opcode_id = (ByteCodeValueType)OpCodeId::ForkReplaceStay;
364
673
        } else if (opcode_id == (ByteCodeValueType)OpCodeId::ForkJump) {
365
0
            opcode_id = (ByteCodeValueType)OpCodeId::ForkReplaceJump;
366
399
        } else if (opcode_id == (ByteCodeValueType)OpCodeId::JumpNonEmpty) {
367
399
            auto& jump_opcode_id = bytecode[candidate.forking_block.end + 3];
368
399
            if (jump_opcode_id == (ByteCodeValueType)OpCodeId::ForkStay)
369
0
                jump_opcode_id = (ByteCodeValueType)OpCodeId::ForkReplaceStay;
370
399
            else if (jump_opcode_id == (ByteCodeValueType)OpCodeId::ForkJump)
371
399
                jump_opcode_id = (ByteCodeValueType)OpCodeId::ForkReplaceJump;
372
0
            else
373
399
                VERIFY_NOT_REACHED();
374
399
        } else {
375
0
            VERIFY_NOT_REACHED();
376
0
        }
377
1.07k
    }
378
379
1.07k
    if (!needed_patches.is_empty()) {
380
0
        MatchState state;
381
0
        auto bytecode_size = bytecode.size();
382
0
        state.instruction_position = 0;
383
0
        struct Patch {
384
0
            ssize_t value;
385
0
            size_t offset;
386
0
            bool should_negate { false };
387
0
        };
388
0
        for (;;) {
389
0
            if (state.instruction_position >= bytecode_size)
390
0
                break;
391
392
0
            auto& opcode = bytecode.get_opcode(state);
393
0
            Stack<Patch, 2> patch_points;
394
395
0
            switch (opcode.opcode_id()) {
396
0
            case OpCodeId::Jump:
397
0
                patch_points.push({ static_cast<OpCode_Jump const&>(opcode).offset(), state.instruction_position + 1 });
398
0
                break;
399
0
            case OpCodeId::JumpNonEmpty:
400
0
                patch_points.push({ static_cast<OpCode_JumpNonEmpty const&>(opcode).offset(), state.instruction_position + 1 });
401
0
                patch_points.push({ static_cast<OpCode_JumpNonEmpty const&>(opcode).checkpoint(), state.instruction_position + 2 });
402
0
                break;
403
0
            case OpCodeId::ForkJump:
404
0
                patch_points.push({ static_cast<OpCode_ForkJump const&>(opcode).offset(), state.instruction_position + 1 });
405
0
                break;
406
0
            case OpCodeId::ForkStay:
407
0
                patch_points.push({ static_cast<OpCode_ForkStay const&>(opcode).offset(), state.instruction_position + 1 });
408
0
                break;
409
0
            case OpCodeId::Repeat:
410
0
                patch_points.push({ -(ssize_t) static_cast<OpCode_Repeat const&>(opcode).offset(), state.instruction_position + 1, true });
411
0
                break;
412
0
            default:
413
0
                break;
414
0
            }
415
416
0
            while (!patch_points.is_empty()) {
417
0
                auto& patch_point = patch_points.top();
418
0
                auto target_offset = patch_point.value + state.instruction_position + opcode.size();
419
420
0
                constexpr auto do_patch = [](auto& patch_it, auto& patch_point, auto& target_offset, auto& bytecode, auto ip) {
421
0
                    if (patch_it.key() == ip)
422
0
                        return;
423
424
0
                    if (patch_point.value < 0 && target_offset <= patch_it.key() && ip > patch_it.key())
425
0
                        bytecode[patch_point.offset] += (patch_point.should_negate ? 1 : -1) * (*patch_it);
426
0
                    else if (patch_point.value > 0 && target_offset >= patch_it.key() && ip < patch_it.key())
427
0
                        bytecode[patch_point.offset] += (patch_point.should_negate ? -1 : 1) * (*patch_it);
428
0
                };
Unexecuted instantiation: auto regex::Regex<regex::PosixBasicParser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)::{lambda(auto:1&, auto:2&, auto:3&, auto:4&, auto:5)#1}::operator()<AK::RedBlackTreeIterator<AK::RedBlackTree<unsigned long, unsigned long> const, unsigned long const>, regex::Regex<regex::PosixBasicParser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)::Patch, unsigned long, regex::ByteCode, unsigned long>(AK::RedBlackTreeIterator<AK::RedBlackTree<unsigned long, unsigned long> const, unsigned long const>&, regex::Regex<regex::PosixBasicParser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)::Patch&, unsigned long&, regex::ByteCode&, unsigned long) const
Unexecuted instantiation: auto regex::Regex<regex::PosixExtendedParser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)::{lambda(auto:1&, auto:2&, auto:3&, auto:4&, auto:5)#1}::operator()<AK::RedBlackTreeIterator<AK::RedBlackTree<unsigned long, unsigned long> const, unsigned long const>, regex::Regex<regex::PosixExtendedParser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)::Patch, unsigned long, regex::ByteCode, unsigned long>(AK::RedBlackTreeIterator<AK::RedBlackTree<unsigned long, unsigned long> const, unsigned long const>&, regex::Regex<regex::PosixExtendedParser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)::Patch&, unsigned long&, regex::ByteCode&, unsigned long) const
Unexecuted instantiation: auto regex::Regex<regex::ECMA262Parser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)::{lambda(auto:1&, auto:2&, auto:3&, auto:4&, auto:5)#1}::operator()<AK::RedBlackTreeIterator<AK::RedBlackTree<unsigned long, unsigned long> const, unsigned long const>, regex::Regex<regex::ECMA262Parser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)::Patch, unsigned long, regex::ByteCode, unsigned long>(AK::RedBlackTreeIterator<AK::RedBlackTree<unsigned long, unsigned long> const, unsigned long const>&, regex::Regex<regex::ECMA262Parser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)::Patch&, unsigned long&, regex::ByteCode&, unsigned long) const
429
430
0
                if (auto patch_it = needed_patches.find_largest_not_above_iterator(target_offset); !patch_it.is_end())
431
0
                    do_patch(patch_it, patch_point, target_offset, bytecode, state.instruction_position);
432
0
                else if (auto patch_it = needed_patches.find_largest_not_above_iterator(state.instruction_position); !patch_it.is_end())
433
0
                    do_patch(patch_it, patch_point, target_offset, bytecode, state.instruction_position);
434
435
0
                patch_points.pop();
436
0
            }
437
438
0
            state.instruction_position += opcode.size();
439
0
        }
440
0
    }
441
442
1.07k
    if constexpr (REGEX_DEBUG) {
443
1.07k
        warnln("Transformed to:");
444
1.07k
        RegexDebug dbg;
445
1.07k
        dbg.print_bytecode(*this);
446
1.07k
    }
447
1.07k
}
regex::Regex<regex::PosixBasicParser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)
Line
Count
Source
215
4.20k
{
216
4.20k
    auto& bytecode = parser_result.bytecode;
217
4.20k
    if constexpr (REGEX_DEBUG) {
218
4.20k
        RegexDebug dbg;
219
4.20k
        dbg.print_bytecode(*this);
220
4.20k
        for (auto const& block : basic_blocks)
221
4.20k
            dbgln("block from {} to {}", block.start, block.end);
222
4.20k
    }
223
224
    // A pattern such as:
225
    //     bb0       |  RE0
226
    //               |  ForkX bb0
227
    //     -------------------------
228
    //     bb1       |  RE1
229
    // can be rewritten as:
230
    //     -------------------------
231
    //     bb0       | RE0
232
    //               | ForkReplaceX bb0
233
    //     -------------------------
234
    //     bb1       | RE1
235
    // provided that first(RE1) not-in end(RE0), which is to say
236
    // that RE1 cannot start with whatever RE0 has matched (ever).
237
    //
238
    // Alternatively, a second form of this pattern can also occur:
239
    //     bb0 | *
240
    //         | ForkX bb2
241
    //     ------------------------
242
    //     bb1 | RE0
243
    //         | Jump bb0
244
    //     ------------------------
245
    //     bb2 | RE1
246
    // which can be transformed (with the same preconditions) to:
247
    //     bb0 | *
248
    //         | ForkReplaceX bb2
249
    //     ------------------------
250
    //     bb1 | RE0
251
    //         | Jump bb0
252
    //     ------------------------
253
    //     bb2 | RE1
254
255
4.20k
    enum class AlternateForm {
256
4.20k
        DirectLoopWithoutHeader,               // loop without proper header, a block forking to itself. i.e. the first form.
257
4.20k
        DirectLoopWithoutHeaderAndEmptyFollow, // loop without proper header, a block forking to itself. i.e. the first form but with RE1 being empty.
258
4.20k
        DirectLoopWithHeader,                  // loop with proper header, i.e. the second form.
259
4.20k
    };
260
4.20k
    struct CandidateBlock {
261
4.20k
        Block forking_block;
262
4.20k
        Optional<Block> new_target_block;
263
4.20k
        AlternateForm form;
264
4.20k
    };
265
4.20k
    Vector<CandidateBlock> candidate_blocks;
266
267
4.20k
    auto is_an_eligible_jump = [](OpCode const& opcode, size_t ip, size_t block_start, AlternateForm alternate_form) {
268
4.20k
        switch (opcode.opcode_id()) {
269
4.20k
        case OpCodeId::JumpNonEmpty: {
270
4.20k
            auto const& op = static_cast<OpCode_JumpNonEmpty const&>(opcode);
271
4.20k
            auto form = op.form();
272
4.20k
            if (form != OpCodeId::Jump && alternate_form == AlternateForm::DirectLoopWithHeader)
273
4.20k
                return false;
274
4.20k
            if (form != OpCodeId::ForkJump && form != OpCodeId::ForkStay && alternate_form == AlternateForm::DirectLoopWithoutHeader)
275
4.20k
                return false;
276
4.20k
            return op.offset() + ip + opcode.size() == block_start;
277
4.20k
        }
278
4.20k
        case OpCodeId::ForkJump:
279
4.20k
            if (alternate_form == AlternateForm::DirectLoopWithHeader)
280
4.20k
                return false;
281
4.20k
            return static_cast<OpCode_ForkJump const&>(opcode).offset() + ip + opcode.size() == block_start;
282
4.20k
        case OpCodeId::ForkStay:
283
4.20k
            if (alternate_form == AlternateForm::DirectLoopWithHeader)
284
4.20k
                return false;
285
4.20k
            return static_cast<OpCode_ForkStay const&>(opcode).offset() + ip + opcode.size() == block_start;
286
4.20k
        case OpCodeId::Jump:
287
            // Infinite loop does *not* produce forks.
288
4.20k
            if (alternate_form == AlternateForm::DirectLoopWithoutHeader)
289
4.20k
                return false;
290
4.20k
            if (alternate_form == AlternateForm::DirectLoopWithHeader)
291
4.20k
                return static_cast<OpCode_Jump const&>(opcode).offset() + ip + opcode.size() == block_start;
292
4.20k
            VERIFY_NOT_REACHED();
293
4.20k
        default:
294
4.20k
            return false;
295
4.20k
        }
296
4.20k
    };
297
23.2M
    for (size_t i = 0; i < basic_blocks.size(); ++i) {
298
23.2M
        auto forking_block = basic_blocks[i];
299
23.2M
        Optional<Block> fork_fallback_block;
300
23.2M
        if (i + 1 < basic_blocks.size())
301
23.2M
            fork_fallback_block = basic_blocks[i + 1];
302
23.2M
        MatchState state;
303
        // Check if the last instruction in this block is a jump to the block itself:
304
23.2M
        {
305
23.2M
            state.instruction_position = forking_block.end;
306
23.2M
            auto& opcode = bytecode.get_opcode(state);
307
23.2M
            if (is_an_eligible_jump(opcode, state.instruction_position, forking_block.start, AlternateForm::DirectLoopWithoutHeader)) {
308
                // We've found RE0 (and RE1 is just the following block, if any), let's see if the precondition applies.
309
                // if RE1 is empty, there's no first(RE1), so this is an automatic pass.
310
190k
                if (!fork_fallback_block.has_value() || fork_fallback_block->end == fork_fallback_block->start) {
311
114
                    candidate_blocks.append({ forking_block, fork_fallback_block, AlternateForm::DirectLoopWithoutHeader });
312
114
                    break;
313
114
                }
314
315
190k
                auto precondition = block_satisfies_atomic_rewrite_precondition(bytecode, forking_block, *fork_fallback_block);
316
190k
                if (precondition == AtomicRewritePreconditionResult::SatisfiedWithProperHeader) {
317
221
                    candidate_blocks.append({ forking_block, fork_fallback_block, AlternateForm::DirectLoopWithoutHeader });
318
221
                    break;
319
221
                }
320
190k
                if (precondition == AtomicRewritePreconditionResult::SatisfiedWithEmptyHeader) {
321
64
                    candidate_blocks.append({ forking_block, fork_fallback_block, AlternateForm::DirectLoopWithoutHeaderAndEmptyFollow });
322
64
                    break;
323
64
                }
324
190k
            }
325
23.2M
        }
326
        // Check if the last instruction in the last block is a direct jump to this block
327
23.2M
        if (fork_fallback_block.has_value()) {
328
23.2M
            state.instruction_position = fork_fallback_block->end;
329
23.2M
            auto& opcode = bytecode.get_opcode(state);
330
23.2M
            if (is_an_eligible_jump(opcode, state.instruction_position, forking_block.start, AlternateForm::DirectLoopWithHeader)) {
331
                // We've found bb1 and bb0, let's just make sure that bb0 forks to bb2.
332
1.57M
                state.instruction_position = forking_block.end;
333
1.57M
                auto& opcode = bytecode.get_opcode(state);
334
1.57M
                if (opcode.opcode_id() == OpCodeId::ForkJump || opcode.opcode_id() == OpCodeId::ForkStay) {
335
1.57M
                    Optional<Block> block_following_fork_fallback;
336
1.57M
                    if (i + 2 < basic_blocks.size())
337
1.57M
                        block_following_fork_fallback = basic_blocks[i + 2];
338
1.57M
                    if (!block_following_fork_fallback.has_value()
339
1.57M
                        || block_satisfies_atomic_rewrite_precondition(bytecode, *fork_fallback_block, *block_following_fork_fallback) != AtomicRewritePreconditionResult::NotSatisfied) {
340
673
                        candidate_blocks.append({ forking_block, {}, AlternateForm::DirectLoopWithHeader });
341
673
                        break;
342
673
                    }
343
1.57M
                }
344
1.57M
            }
345
23.2M
        }
346
23.2M
    }
347
348
4.20k
    dbgln_if(REGEX_DEBUG, "Found {} candidate blocks", candidate_blocks.size());
349
4.20k
    if (candidate_blocks.is_empty()) {
350
3.13k
        dbgln_if(REGEX_DEBUG, "Failed to find anything for {}", pattern_value);
351
3.13k
        return;
352
3.13k
    }
353
354
1.07k
    RedBlackTree<size_t, size_t> needed_patches;
355
356
    // Reverse the blocks, so we can patch the bytecode without messing with the latter patches.
357
1.07k
    quick_sort(candidate_blocks, [](auto& a, auto& b) { return b.forking_block.start > a.forking_block.start; });
358
1.07k
    for (auto& candidate : candidate_blocks) {
359
        // Note that both forms share a ForkReplace patch in forking_block.
360
        // Patch the ForkX in forking_block to be a ForkReplaceX instead.
361
1.07k
        auto& opcode_id = bytecode[candidate.forking_block.end];
362
1.07k
        if (opcode_id == (ByteCodeValueType)OpCodeId::ForkStay) {
363
673
            opcode_id = (ByteCodeValueType)OpCodeId::ForkReplaceStay;
364
673
        } else if (opcode_id == (ByteCodeValueType)OpCodeId::ForkJump) {
365
0
            opcode_id = (ByteCodeValueType)OpCodeId::ForkReplaceJump;
366
399
        } else if (opcode_id == (ByteCodeValueType)OpCodeId::JumpNonEmpty) {
367
399
            auto& jump_opcode_id = bytecode[candidate.forking_block.end + 3];
368
399
            if (jump_opcode_id == (ByteCodeValueType)OpCodeId::ForkStay)
369
0
                jump_opcode_id = (ByteCodeValueType)OpCodeId::ForkReplaceStay;
370
399
            else if (jump_opcode_id == (ByteCodeValueType)OpCodeId::ForkJump)
371
399
                jump_opcode_id = (ByteCodeValueType)OpCodeId::ForkReplaceJump;
372
0
            else
373
399
                VERIFY_NOT_REACHED();
374
399
        } else {
375
0
            VERIFY_NOT_REACHED();
376
0
        }
377
1.07k
    }
378
379
1.07k
    if (!needed_patches.is_empty()) {
380
0
        MatchState state;
381
0
        auto bytecode_size = bytecode.size();
382
0
        state.instruction_position = 0;
383
0
        struct Patch {
384
0
            ssize_t value;
385
0
            size_t offset;
386
0
            bool should_negate { false };
387
0
        };
388
0
        for (;;) {
389
0
            if (state.instruction_position >= bytecode_size)
390
0
                break;
391
392
0
            auto& opcode = bytecode.get_opcode(state);
393
0
            Stack<Patch, 2> patch_points;
394
395
0
            switch (opcode.opcode_id()) {
396
0
            case OpCodeId::Jump:
397
0
                patch_points.push({ static_cast<OpCode_Jump const&>(opcode).offset(), state.instruction_position + 1 });
398
0
                break;
399
0
            case OpCodeId::JumpNonEmpty:
400
0
                patch_points.push({ static_cast<OpCode_JumpNonEmpty const&>(opcode).offset(), state.instruction_position + 1 });
401
0
                patch_points.push({ static_cast<OpCode_JumpNonEmpty const&>(opcode).checkpoint(), state.instruction_position + 2 });
402
0
                break;
403
0
            case OpCodeId::ForkJump:
404
0
                patch_points.push({ static_cast<OpCode_ForkJump const&>(opcode).offset(), state.instruction_position + 1 });
405
0
                break;
406
0
            case OpCodeId::ForkStay:
407
0
                patch_points.push({ static_cast<OpCode_ForkStay const&>(opcode).offset(), state.instruction_position + 1 });
408
0
                break;
409
0
            case OpCodeId::Repeat:
410
0
                patch_points.push({ -(ssize_t) static_cast<OpCode_Repeat const&>(opcode).offset(), state.instruction_position + 1, true });
411
0
                break;
412
0
            default:
413
0
                break;
414
0
            }
415
416
0
            while (!patch_points.is_empty()) {
417
0
                auto& patch_point = patch_points.top();
418
0
                auto target_offset = patch_point.value + state.instruction_position + opcode.size();
419
420
0
                constexpr auto do_patch = [](auto& patch_it, auto& patch_point, auto& target_offset, auto& bytecode, auto ip) {
421
0
                    if (patch_it.key() == ip)
422
0
                        return;
423
424
0
                    if (patch_point.value < 0 && target_offset <= patch_it.key() && ip > patch_it.key())
425
0
                        bytecode[patch_point.offset] += (patch_point.should_negate ? 1 : -1) * (*patch_it);
426
0
                    else if (patch_point.value > 0 && target_offset >= patch_it.key() && ip < patch_it.key())
427
0
                        bytecode[patch_point.offset] += (patch_point.should_negate ? -1 : 1) * (*patch_it);
428
0
                };
429
430
0
                if (auto patch_it = needed_patches.find_largest_not_above_iterator(target_offset); !patch_it.is_end())
431
0
                    do_patch(patch_it, patch_point, target_offset, bytecode, state.instruction_position);
432
0
                else if (auto patch_it = needed_patches.find_largest_not_above_iterator(state.instruction_position); !patch_it.is_end())
433
0
                    do_patch(patch_it, patch_point, target_offset, bytecode, state.instruction_position);
434
435
0
                patch_points.pop();
436
0
            }
437
438
0
            state.instruction_position += opcode.size();
439
0
        }
440
0
    }
441
442
1.07k
    if constexpr (REGEX_DEBUG) {
443
1.07k
        warnln("Transformed to:");
444
1.07k
        RegexDebug dbg;
445
1.07k
        dbg.print_bytecode(*this);
446
1.07k
    }
447
1.07k
}
Unexecuted instantiation: regex::Regex<regex::PosixExtendedParser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)
Unexecuted instantiation: regex::Regex<regex::ECMA262Parser>::attempt_rewrite_loops_as_atomic_groups(AK::Vector<regex::Detail::Block, 0ul> const&)
448
449
void Optimizer::append_alternation(ByteCode& target, ByteCode&& left, ByteCode&& right)
450
0
{
451
0
    Array<ByteCode, 2> alternatives;
452
0
    alternatives[0] = move(left);
453
0
    alternatives[1] = move(right);
454
455
0
    append_alternation(target, alternatives);
456
0
}
457
458
void Optimizer::append_alternation(ByteCode& target, Span<ByteCode> alternatives)
459
0
{
460
0
    if (alternatives.size() == 0)
461
0
        return;
462
463
0
    if (alternatives.size() == 1)
464
0
        return target.extend(move(alternatives[0]));
465
466
0
    if (all_of(alternatives, [](auto& x) { return x.is_empty(); }))
467
0
        return;
468
469
0
    for (auto& entry : alternatives)
470
0
        entry.flatten();
471
472
#if REGEX_DEBUG
473
    ScopeLogger<true> log;
474
    warnln("Alternations:");
475
    RegexDebug dbg;
476
    for (auto& entry : alternatives) {
477
        warnln("----------");
478
        dbg.print_bytecode(entry);
479
    }
480
    ScopeGuard print_at_end {
481
        [&] {
482
            warnln("======================");
483
            RegexDebug dbg;
484
            dbg.print_bytecode(target);
485
        }
486
    };
487
#endif
488
489
0
    Vector<Vector<Detail::Block>> basic_blocks;
490
0
    basic_blocks.ensure_capacity(alternatives.size());
491
492
0
    for (auto& entry : alternatives)
493
0
        basic_blocks.append(Regex<PosixBasicParser>::split_basic_blocks(entry));
494
495
0
    size_t left_skip = 0;
496
0
    size_t shared_block_count = basic_blocks.first().size();
497
0
    for (auto& entry : basic_blocks)
498
0
        shared_block_count = min(shared_block_count, entry.size());
499
500
0
    MatchState state;
501
0
    for (size_t block_index = 0; block_index < shared_block_count; block_index++) {
502
0
        auto& left_block = basic_blocks.first()[block_index];
503
0
        auto left_end = block_index + 1 == basic_blocks.first().size() ? left_block.end : basic_blocks.first()[block_index + 1].start;
504
0
        auto can_continue = true;
505
0
        for (size_t i = 1; i < alternatives.size(); ++i) {
506
0
            auto& right_blocks = basic_blocks[i];
507
0
            auto& right_block = right_blocks[block_index];
508
0
            auto right_end = block_index + 1 == right_blocks.size() ? right_block.end : right_blocks[block_index + 1].start;
509
510
0
            if (left_end - left_block.start != right_end - right_block.start) {
511
0
                can_continue = false;
512
0
                break;
513
0
            }
514
515
0
            if (alternatives[0].spans().slice(left_block.start, left_end - left_block.start) != alternatives[i].spans().slice(right_block.start, right_end - right_block.start)) {
516
0
                can_continue = false;
517
0
                break;
518
0
            }
519
0
        }
520
0
        if (!can_continue)
521
0
            break;
522
523
0
        size_t i = 0;
524
0
        for (auto& entry : alternatives) {
525
0
            auto& blocks = basic_blocks[i];
526
0
            auto& block = blocks[block_index];
527
0
            auto end = block_index + 1 == blocks.size() ? block.end : blocks[block_index + 1].start;
528
0
            state.instruction_position = block.start;
529
0
            size_t skip = 0;
530
0
            while (state.instruction_position < end) {
531
0
                auto& opcode = entry.get_opcode(state);
532
0
                state.instruction_position += opcode.size();
533
0
                skip = state.instruction_position;
534
0
            }
535
0
            left_skip = min(skip, left_skip);
536
0
        }
537
0
    }
538
539
0
    dbgln_if(REGEX_DEBUG, "Skipping {}/{} bytecode entries from {}", left_skip, 0, alternatives[0].size());
540
541
0
    if (left_skip > 0) {
542
0
        target.extend(alternatives[0].release_slice(basic_blocks.first().first().start, left_skip));
543
0
        auto first = true;
544
0
        for (auto& entry : alternatives) {
545
0
            if (first) {
546
0
                first = false;
547
0
                continue;
548
0
            }
549
0
            entry = entry.release_slice(left_skip);
550
0
        }
551
0
    }
552
553
0
    if (all_of(alternatives, [](auto& entry) { return entry.is_empty(); }))
554
0
        return;
555
556
0
    size_t patch_start = target.size();
557
0
    for (size_t i = 1; i < alternatives.size(); ++i) {
558
0
        target.empend(static_cast<ByteCodeValueType>(OpCodeId::ForkJump));
559
0
        target.empend(0u); // To be filled later.
560
0
    }
561
562
0
    size_t size_to_jump = 0;
563
0
    bool seen_one_empty = false;
564
0
    for (size_t i = alternatives.size(); i > 0; --i) {
565
0
        auto& entry = alternatives[i - 1];
566
0
        if (entry.is_empty()) {
567
0
            if (seen_one_empty)
568
0
                continue;
569
0
            seen_one_empty = true;
570
0
        }
571
572
0
        auto is_first = i == 1;
573
0
        auto instruction_size = entry.size() + (is_first ? 0 : 2); // Jump; -> +2
574
0
        size_to_jump += instruction_size;
575
576
0
        if (!is_first)
577
0
            target[patch_start + (i - 2) * 2 + 1] = size_to_jump + (alternatives.size() - i) * 2;
578
579
0
        dbgln_if(REGEX_DEBUG, "{} size = {}, cum={}", i - 1, instruction_size, size_to_jump);
580
0
    }
581
582
0
    seen_one_empty = false;
583
0
    for (size_t i = alternatives.size(); i > 0; --i) {
584
0
        auto& chunk = alternatives[i - 1];
585
0
        if (chunk.is_empty()) {
586
0
            if (seen_one_empty)
587
0
                continue;
588
0
            seen_one_empty = true;
589
0
        }
590
591
0
        ByteCode* previous_chunk = nullptr;
592
0
        size_t j = i - 1;
593
0
        auto seen_one_empty_before = chunk.is_empty();
594
0
        while (j >= 1) {
595
0
            --j;
596
0
            auto& candidate_chunk = alternatives[j];
597
0
            if (candidate_chunk.is_empty()) {
598
0
                if (seen_one_empty_before)
599
0
                    continue;
600
0
            }
601
0
            previous_chunk = &candidate_chunk;
602
0
            break;
603
0
        }
604
605
0
        size_to_jump -= chunk.size() + (previous_chunk ? 2 : 0);
606
607
0
        target.extend(move(chunk));
608
0
        target.empend(static_cast<ByteCodeValueType>(OpCodeId::Jump));
609
0
        target.empend(size_to_jump); // Jump to the _END label
610
0
    }
611
0
}
612
613
enum class LookupTableInsertionOutcome {
614
    Successful,
615
    ReplaceWithAnyChar,
616
    TemporaryInversionNeeded,
617
    PermanentInversionNeeded,
618
    CannotPlaceInTable,
619
};
620
static LookupTableInsertionOutcome insert_into_lookup_table(RedBlackTree<ByteCodeValueType, CharRange>& table, CompareTypeAndValuePair pair)
621
17.9M
{
622
17.9M
    switch (pair.type) {
623
2.99k
    case CharacterCompareType::Inverse:
624
2.99k
        return LookupTableInsertionOutcome::PermanentInversionNeeded;
625
0
    case CharacterCompareType::TemporaryInverse:
626
0
        return LookupTableInsertionOutcome::TemporaryInversionNeeded;
627
0
    case CharacterCompareType::AnyChar:
628
0
        return LookupTableInsertionOutcome::ReplaceWithAnyChar;
629
90.8k
    case CharacterCompareType::CharClass:
630
90.8k
        return LookupTableInsertionOutcome::CannotPlaceInTable;
631
15.0M
    case CharacterCompareType::Char:
632
15.0M
        table.insert(pair.value, { (u32)pair.value, (u32)pair.value });
633
15.0M
        break;
634
2.84M
    case CharacterCompareType::CharRange: {
635
2.84M
        CharRange range { pair.value };
636
2.84M
        table.insert(range.from, range);
637
2.84M
        break;
638
0
    }
639
0
    case CharacterCompareType::Reference:
640
0
    case CharacterCompareType::Property:
641
0
    case CharacterCompareType::GeneralCategory:
642
0
    case CharacterCompareType::Script:
643
0
    case CharacterCompareType::ScriptExtension:
644
0
        return LookupTableInsertionOutcome::CannotPlaceInTable;
645
0
    case CharacterCompareType::Undefined:
646
0
    case CharacterCompareType::RangeExpressionDummy:
647
0
    case CharacterCompareType::String:
648
0
    case CharacterCompareType::LookupTable:
649
0
        VERIFY_NOT_REACHED();
650
17.9M
    }
651
652
17.8M
    return LookupTableInsertionOutcome::Successful;
653
17.9M
}
654
655
void Optimizer::append_character_class(ByteCode& target, Vector<CompareTypeAndValuePair>&& pairs)
656
12.7M
{
657
12.7M
    ByteCode arguments;
658
12.7M
    size_t argument_count = 0;
659
660
12.7M
    if (pairs.size() <= 1) {
661
12.7M
        for (auto& pair : pairs) {
662
12.7M
            arguments.append(to_underlying(pair.type));
663
12.7M
            if (pair.type != CharacterCompareType::AnyChar && pair.type != CharacterCompareType::TemporaryInverse && pair.type != CharacterCompareType::Inverse)
664
12.7M
                arguments.append(pair.value);
665
12.7M
            ++argument_count;
666
12.7M
        }
667
12.7M
    } else {
668
6.65k
        RedBlackTree<ByteCodeValueType, CharRange> table;
669
6.65k
        RedBlackTree<ByteCodeValueType, CharRange> inverted_table;
670
6.65k
        auto* current_table = &table;
671
6.65k
        auto* current_inverted_table = &inverted_table;
672
6.65k
        bool invert_for_next_iteration = false;
673
6.65k
        bool is_currently_inverted = false;
674
675
17.9M
        for (auto& value : pairs) {
676
17.9M
            auto should_invert_after_this_iteration = invert_for_next_iteration;
677
17.9M
            invert_for_next_iteration = false;
678
679
17.9M
            auto insertion_result = insert_into_lookup_table(*current_table, value);
680
17.9M
            switch (insertion_result) {
681
17.8M
            case LookupTableInsertionOutcome::Successful:
682
17.8M
                break;
683
0
            case LookupTableInsertionOutcome::ReplaceWithAnyChar: {
684
0
                table.clear();
685
0
                inverted_table.clear();
686
0
                arguments.append(to_underlying(CharacterCompareType::AnyChar));
687
0
                ++argument_count;
688
0
                break;
689
0
            }
690
0
            case LookupTableInsertionOutcome::TemporaryInversionNeeded:
691
0
                swap(current_table, current_inverted_table);
692
0
                invert_for_next_iteration = true;
693
0
                is_currently_inverted = !is_currently_inverted;
694
0
                break;
695
2.99k
            case LookupTableInsertionOutcome::PermanentInversionNeeded:
696
2.99k
                swap(current_table, current_inverted_table);
697
2.99k
                is_currently_inverted = !is_currently_inverted;
698
2.99k
                break;
699
90.8k
            case LookupTableInsertionOutcome::CannotPlaceInTable:
700
90.8k
                if (is_currently_inverted) {
701
90.1k
                    arguments.append(to_underlying(CharacterCompareType::TemporaryInverse));
702
90.1k
                    ++argument_count;
703
90.1k
                }
704
90.8k
                arguments.append(to_underlying(value.type));
705
90.8k
                arguments.append(value.value);
706
90.8k
                ++argument_count;
707
90.8k
                break;
708
17.9M
            }
709
710
17.9M
            if (should_invert_after_this_iteration) {
711
0
                swap(current_table, current_inverted_table);
712
0
                is_currently_inverted = !is_currently_inverted;
713
0
            }
714
17.9M
        }
715
6.65k
        auto append_table = [&](auto& table) {
716
6.61k
            ++argument_count;
717
6.61k
            arguments.append(to_underlying(CharacterCompareType::LookupTable));
718
6.61k
            auto size_index = arguments.size();
719
6.61k
            arguments.append(0);
720
6.61k
            Optional<CharRange> active_range;
721
6.61k
            size_t range_count = 0;
722
17.8M
            for (auto& range : table) {
723
17.8M
                if (!active_range.has_value()) {
724
6.61k
                    active_range = range;
725
6.61k
                    continue;
726
6.61k
                }
727
728
17.8M
                if (range.from <= active_range->to + 1 && range.to + 1 >= active_range->from) {
729
14.1M
                    active_range = CharRange { min(range.from, active_range->from), max(range.to, active_range->to) };
730
14.1M
                } else {
731
3.65M
                    ++range_count;
732
3.65M
                    arguments.append(active_range.release_value());
733
3.65M
                    active_range = range;
734
3.65M
                }
735
17.8M
            }
736
6.61k
            if (active_range.has_value()) {
737
6.61k
                ++range_count;
738
6.61k
                arguments.append(active_range.release_value());
739
6.61k
            }
740
6.61k
            arguments[size_index] = range_count;
741
6.61k
        };
742
743
6.65k
        if (!table.is_empty())
744
3.63k
            append_table(table);
745
746
6.65k
        if (!inverted_table.is_empty()) {
747
2.97k
            ++argument_count;
748
2.97k
            arguments.append(to_underlying(CharacterCompareType::TemporaryInverse));
749
2.97k
            append_table(inverted_table);
750
2.97k
        }
751
6.65k
    }
752
753
12.7M
    target.empend(static_cast<ByteCodeValueType>(OpCodeId::Compare));
754
12.7M
    target.empend(argument_count);   // number of arguments
755
12.7M
    target.empend(arguments.size()); // size of arguments
756
12.7M
    target.extend(move(arguments));
757
12.7M
}
758
759
template void Regex<PosixBasicParser>::run_optimization_passes();
760
template void Regex<PosixExtendedParser>::run_optimization_passes();
761
template void Regex<ECMA262Parser>::run_optimization_passes();
762
}