[patch v3 1/4][libstdc++]: Continue with regex DFS traversals without next frames [PR126274]
Jonathan Wakely
jwakely@redhat.com
Fri Sep 18 12:07:45 GMT 2026
On Fri, 18 Sep 2026 at 13:00 +0100, Jonathan Wakely wrote:
>N.B. this mail had exactly the same MessageId as [patch v2 1/4] so my
>download scripts (based on 'b4 mbox') refused to fetch [patch v3 1/4].
>
>The duplicate MessageId is patch-20701-rb-rev-unknown-tamar@arm.com
>which is not very unique.
>
>On Thu, 20 Aug 2026 at 18:13 +0100, Tamar Christina wrote:
>>The change in r16-7193-g158ad5f96954da5fa24d5c2a91ae92417fb62e20 changed
>>the recursive implementation with an iterative one using an explicit heap.
>>
>>However one benefit of the previous implementation is that the frame did
>>not have to be saved and popped when the match is supposed to continue.
>>
>>This means that on hot paths we now have additional memory accesses and
>>need additional instructions to calculate the memref addresses.
>>
>>For DFS matching this is clearly suboptimal since when _M_rep_once_more
>>then we push and pop the same state but there is enough other acceses
>>in between the push and pop that we get a lot of cache misses.
>>
>>This makes all the private _m_handle_* methods return a _StateIdT which allows
>>the caller to deal with the value, so that for DFS we can avoid pushing the
>>frame if needed.
>>
>>For DFS we try to consume the state immediately until we're told to
>>stop.
>>
>>For this to work the methods have to me marked always inline, because a key part
>>of the optimization is to keep the values in registers rather than passing
>>through stack and the function call overheads and AAPCS requirements would
>>negate the benefits.
>>
>>The patch also reserves some frames in the initial vector to avoid having
>>resizes on the hot path. To avoid large RSS before matching even starts
>>we provide a cap to the initial reservations. However I have not yet addressed
>>
>>Jakub's comment that the cap at 255 is likely to big. I need to do more
>>experiments here to figure out if it's even needed. For now I left it since I
>>am expecting another respin here.
>>
>>The __dfs_mode changes are because the constexpr patch still gave a big boost so
>>it prepares to apply it.
>>
>>There is still a regression until the end of the series and each patch
>>will chip away at it.
>>
>>Also note that with none of these changes do I see an increase heap or stack
>>usage that the original fix fixed. RSS stays about the same.
>>
>>PS. thanks for the link to the algorithm in the source, it was useful to
>>understand how the machinery works!
>>
>>Benchmark improvements vs GCC 16:
>>
>>at -O2:
>>
>> email: +36.1%
>> URI: +36.5%
>> IPv4 +33.0%
>>
>>at -O3:
>>
>> email: +45.5%,
>> URI: +44.9%
>> IPv4: +43.0%
>>
>>On Neoverse-V1
>>
>>Bootstrapped Regtested on aarch64-none-linux-gnu,
>>arm-none-linux-gnueabihf, x86_64-pc-linux-gnu
>>-m32, -m64 and no issues.
>>
>>Ok for master?
>>
>>Thanks,
>>Tamar
>>
>>libstdc++-v3/ChangeLog:
>>
>> PR libstdc++/126274
>> * include/bits/regex_executor.h (_Executor): Reserve frame space.
>> (_M_rep_once_more, _M_handle_repeat, _M_handle_subexpr_begin,
>> _M_handle_subexpr_end, _M_handle_line_begin_assertion,
>> _M_handle_line_end_assertion, _M_handle_word_boundary,
>> _M_handle_subexpr_lookahead, _M_handle_match, _M_handle_backref,
>> _M_node): return StateIdT.
>> (_M_visited): Mark inline.
>> * include/bits/regex_executor.tcc (_M_rep_once_more, _M_handle_repeat,
>> _M_handle_subexpr_begin, _M_handle_subexpr_end,
>> _M_handle_line_begin_assertion, _M_handle_line_end_assertion,
>> _M_handle_word_boundary, _M_handle_subexpr_lookahead, _M_handle_match,
>> _M_handle_backref): Return state, mark always inline.
>> (_M_node): Return StateIdT and also decide what to do with the value
>> after return.
>> (_M_dfs): Traverse states iteratively for _S_fopcode_next,
>> _S_fopcode_fallback_next, _S_fopcode_fallback_rep_once_more
>> and _S_fopcode_rep_once_more.
>>
>>---
>>diff --git a/libstdc++-v3/include/bits/regex.h b/libstdc++-v3/include/bits/regex.h
>>index 0e815ca086eb33975dd3ac66841c6eaf4a54b2ad..dce420251db0817fa05ab79d7ca0838f214fdc33 100644
>>--- a/libstdc++-v3/include/bits/regex.h
>>+++ b/libstdc++-v3/include/bits/regex.h
>>@@ -61,8 +61,10 @@ namespace __detail
>> _RegexExecutorPolicy __policy,
>> bool __match_mode);
>>
>>+_GLIBCXX_BEGIN_INLINE_ABI_NAMESPACE(_V2)
>> template<typename, typename, typename>
>> class _Executor;
>>+_GLIBCXX_END_INLINE_ABI_NAMESPACE(_V2)
>>
>> template<typename _Tp>
>> struct __is_contiguous_iter : false_type { };
>>diff --git a/libstdc++-v3/include/bits/regex_executor.h b/libstdc++-v3/include/bits/regex_executor.h
>>index 797ad702784b6d1bf07734d91683946d989630f5..c2d1d5f0bfeee279fbd1fb138c58f45f9cdbe60e 100644
>>--- a/libstdc++-v3/include/bits/regex_executor.h
>>+++ b/libstdc++-v3/include/bits/regex_executor.h
>>@@ -41,6 +41,7 @@ namespace __detail
>> * @{
>> */
>>
>>+_GLIBCXX_BEGIN_INLINE_ABI_NAMESPACE(_V2)
>
>This moves _ExecutorFrame into namespace _V2 as well, but I don't
>think that's necessary, is it? It hasn't changed, so leaving it alone
>results in slightly shorter mangled names, and fewer distinct symbols
>in an executable if you mix old and new instantiations linked
>together.
>
>I think the macro should be moved 3 lines lower in this file.
For the record, _ExecutorFrame was added in r16-7193-g158ad5f96954da
and it was in _V2 at that revision, but then the _V2 namespace was
removed in r16-7426-gd3af40eefab378, and both commits were on trunk
before 16.1.0 so we never made a release with _V2::_ExecutorFrame. So
I think it's fine to keep it outside _V2.
For _Executor we do need it to be in _V2 because 16.1.0 and 16.2.0
both define std::__detail::_Executor without the _V2 and we need to
make it distinct from that type.
>The macro should be *before* the Doxygen comment block, not between
>the comment and the class template. Otherwise the Doxygen comment
>refers to the inline namespace instead of the class template (in
>practice it won't, because the macro expands to nothing when Doxygen
>preprocesses the header, but in principle the comment should be
>attached directly to the class not a namespace declaration before the
>class).
>
>
>> template<typename _BiIter, bool _Trivial = is_trivially_copyable<_BiIter>::value>
>> struct _ExecutorFrame;
>>
>>@@ -113,43 +114,43 @@ namespace __detail
>> _M_search();
>>
>> private:
>>- void
>>+ _StateIdT
>> _M_rep_once_more(_Match_mode __match_mode, _StateIdT);
>>
>>- void
>>+ _StateIdT
>> _M_handle_repeat(_Match_mode, _StateIdT);
>>
>>- void
>>+ _StateIdT
>> _M_handle_subexpr_begin(_Match_mode, _StateIdT);
>>
>>- void
>>+ _StateIdT
>> _M_handle_subexpr_end(_Match_mode, _StateIdT);
>>
>>- void
>>+ _StateIdT
>> _M_handle_line_begin_assertion(_Match_mode, _StateIdT);
>>
>>- void
>>+ _StateIdT
>> _M_handle_line_end_assertion(_Match_mode, _StateIdT);
>>
>>- void
>>+ _StateIdT
>> _M_handle_word_boundary(_Match_mode, _StateIdT);
>>
>>- void
>>+ _StateIdT
>> _M_handle_subexpr_lookahead(_Match_mode, _StateIdT);
>>
>>- void
>>+ _StateIdT
>> _M_handle_match(_Match_mode, _StateIdT);
>>
>>- void
>>+ _StateIdT
>> _M_handle_backref(_Match_mode, _StateIdT);
>>
>>- void
>>+ _StateIdT
>> _M_handle_accept(_Match_mode, _StateIdT);
>>
>>- void
>>+ _StateIdT
>> _M_handle_alternative(_Match_mode, _StateIdT);
>>
>>- void
>>+ _StateIdT
>> _M_node(_Match_mode, _StateIdT);
>>
>> void
>>@@ -247,7 +248,7 @@ namespace __detail
>> return (_M_re._M_automaton->_M_options() & __m) == __m;
>> }
>>
>>- bool
>>+ inline bool
>> _M_visited(_StateIdT __i)
>> {
>> if (_M_visited_states)
>>@@ -284,6 +285,7 @@ namespace __detail
>> bool _M_has_sol;
>> };
>>
>>+_GLIBCXX_END_INLINE_ABI_NAMESPACE(_V2)
>
>And please put this one immediately after the closing brace of
>_Executor with a blank line after it, not before it. That makes it a
>bit clearer than it's tied to _Executor.
>
>
>> ///@} regex-detail
>>} // namespace __detail
>>_GLIBCXX_END_NAMESPACE_VERSION
>>diff --git a/libstdc++-v3/include/bits/regex_executor.tcc b/libstdc++-v3/include/bits/regex_executor.tcc
>>index 167a7a345300868ed5e0852e328569aec47e3d0d..87d5ac68685c22bbedcf9e67f96b50b61392a701 100644
>>--- a/libstdc++-v3/include/bits/regex_executor.tcc
>>+++ b/libstdc++-v3/include/bits/regex_executor.tcc
>>@@ -36,6 +36,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>#pragma GCC diagnostic ignored "-Wc++17-extensions" // if constexpr
>>namespace __detail
>>{
>>+_GLIBCXX_BEGIN_INLINE_ABI_NAMESPACE(_V2)
>
>I don't think you need the BEGIN and END macros in this file, because
>name lookup for _Executor in the member function definitions will
>automatically find _V2::_Executor because it's an inline namespace.
>
>If the tests still pass with those tweaks to the macro placement, this
>[patch v3 1/4] is OK for trunk.
>
>We want this backported to gcc-16 too, right?
>
>
>> template<typename _BiIter, typename _Alloc, typename _TraitsT>
>> bool _Executor<_BiIter, _Alloc, _TraitsT>::
>> _M_search()
>>@@ -250,8 +251,14 @@ namespace __detail
>> // infinite loop by refusing to continue when it's already been
>> // visited more than twice. It's `twice` instead of `once` because
>> // we need to spare one more time for potential group capture.
>>+ //
>>+ // If the node cannot be re-entered anymore from the current state then return
>>+ // _S_invalid_state_id otherwise return the current state without going
>>+ // through a vector, allowing the caller to decide what to do with the state
>>+ // This is beneficial for DFS since DFS can continue with the next state
>>+ // immediately
>> template<typename _BiIter, typename _Alloc, typename _TraitsT>
>>- void _Executor<_BiIter, _Alloc, _TraitsT>::
>>+ _StateIdT _Executor<_BiIter, _Alloc, _TraitsT>::
>> _M_rep_once_more(_Match_mode, _StateIdT __i)
>> {
>> const auto& __state = _M_nfa[__i];
>>@@ -263,7 +270,7 @@ namespace __detail
>> _M_frames.back()._M_count = __rep_count.second;
>> __rep_count.first = _M_current;
>> __rep_count.second = 1;
>>- _M_frames.emplace_back(_S_fopcode_next, __state._M_alt);
>>+ return __state._M_alt;
>> }
>> else
>> {
>>@@ -271,9 +278,10 @@ namespace __detail
>> {
>> __rep_count.second++;
>> _M_frames.emplace_back(_S_fopcode_decrement_rep_count, __i);
>>- _M_frames.emplace_back(_S_fopcode_next, __state._M_alt);
>>+ return __state._M_alt;
>> }
>> }
>>+ return _S_invalid_state_id;
>> }
>>
>> // _M_alt branch is "match once more", while _M_next is "get me out
>>@@ -281,8 +289,11 @@ namespace __detail
>> // mean the same thing, and we need to choose the correct order under
>> // given greedy mode.
>> template<typename _BiIter, typename _Alloc, typename _TraitsT>
>>- void _Executor<_BiIter, _Alloc, _TraitsT>::
>>- _M_handle_repeat(_Match_mode, _StateIdT __i)
>>+#ifdef __OPTIMIZE__
>>+ [[__gnu__::__always_inline__]]
>>+#endif
>>+ inline _StateIdT _Executor<_BiIter, _Alloc, _TraitsT>::
>>+ _M_handle_repeat(_Match_mode __match_mode, _StateIdT __i)
>> {
>> const auto& __state = _M_nfa[__i];
>> // Greedy.
>>@@ -294,7 +305,7 @@ namespace __detail
>> _M_current);
>> else
>> _M_frames.emplace_back(_S_fopcode_next, __state._M_next);
>>- _M_frames.emplace_back(_S_fopcode_rep_once_more, __i);
>>+ return _M_rep_once_more(__match_mode, __i);
>> }
>> else // Non-greedy mode
>> {
>>@@ -303,7 +314,7 @@ namespace __detail
>> // vice-versa.
>> _M_frames.emplace_back(_S_fopcode_fallback_rep_once_more, __i,
>> _M_current);
>>- _M_frames.emplace_back(_S_fopcode_next, __state._M_next);
>>+ return __state._M_next;
>> }
>> else
>> {
>>@@ -316,97 +327,122 @@ namespace __detail
>> // accepted state *must* be better than a solution that
>> // matches a non-greedy quantifier one more time.
>> _M_frames.emplace_back(_S_fopcode_fallback_rep_once_more, __i);
>>- _M_frames.emplace_back(_S_fopcode_next, __state._M_next);
>>+ return __state._M_next;
>> }
>> }
>> }
>>+ return _S_invalid_state_id;
>> }
>>
>> template<typename _BiIter, typename _Alloc, typename _TraitsT>
>>- void _Executor<_BiIter, _Alloc, _TraitsT>::
>>+#ifdef __OPTIMIZE__
>>+ [[__gnu__::__always_inline__]]
>>+#endif
>>+ inline _StateIdT _Executor<_BiIter, _Alloc, _TraitsT>::
>> _M_handle_subexpr_begin(_Match_mode, _StateIdT __i)
>> {
>> const auto& __state = _M_nfa[__i];
>> auto& __res = _M_cur_results[__state._M_subexpr];
>>- _M_frames.emplace_back(_S_fopcode_restore_cur_results,
>>- static_cast<_StateIdT>(__state._M_subexpr),
>>- __res.first);
>>+ if (_M_nfa._M_has_backref
>>+ || __state._M_subexpr != 0
>>+ || _M_search_mode != _Search_mode::_DFS)
>>+ _M_frames.emplace_back(_S_fopcode_restore_cur_results,
>>+ static_cast<_StateIdT>(__state._M_subexpr),
>>+ __res.first);
>> __res.first = _M_current;
>>- _M_frames.emplace_back(_S_fopcode_next, __state._M_next);
>>+ return __state._M_next;
>> }
>>
>> template<typename _BiIter, typename _Alloc, typename _TraitsT>
>>- void _Executor<_BiIter, _Alloc, _TraitsT>::
>>+#ifdef __OPTIMIZE__
>>+ [[__gnu__::__always_inline__]]
>>+#endif
>>+ inline _StateIdT _Executor<_BiIter, _Alloc, _TraitsT>::
>> _M_handle_subexpr_end(_Match_mode, _StateIdT __i)
>> {
>> const auto& __state = _M_nfa[__i];
>> auto& __res = _M_cur_results[__state._M_subexpr];
>>- _M_frames.emplace_back(_S_fopcode_restore_cur_results,
>>- static_cast<_StateIdT>(__state._M_subexpr),
>>- __res.second);
>>- _M_frames.back()._M_subexpr_end = true;
>>- _M_frames.back()._M_matched = __res.matched;
>>+ if (_M_nfa._M_has_backref
>>+ || __state._M_subexpr != 0
>>+ || _M_search_mode != _Search_mode::_DFS)
>>+ {
>>+ _M_frames.emplace_back(_S_fopcode_restore_cur_results,
>>+ static_cast<_StateIdT>(__state._M_subexpr),
>>+ __res.second);
>>+ _M_frames.back()._M_subexpr_end = true;
>>+ _M_frames.back()._M_matched = __res.matched;
>>+ }
>>+
>> __res.second = _M_current;
>> __res.matched = true;
>>- _M_frames.emplace_back(_S_fopcode_next, __state._M_next);
>>+ return __state._M_next;
>> }
>>
>> template<typename _BiIter, typename _Alloc, typename _TraitsT>
>>- inline void _Executor<_BiIter, _Alloc, _TraitsT>::
>>+ inline _StateIdT _Executor<_BiIter, _Alloc, _TraitsT>::
>> _M_handle_line_begin_assertion(_Match_mode, _StateIdT __i)
>> {
>> const auto& __state = _M_nfa[__i];
>> if (_M_at_begin())
>>- _M_frames.emplace_back(_S_fopcode_next, __state._M_next);
>>+ return __state._M_next;
>>+ return _S_invalid_state_id;
>> }
>>
>> template<typename _BiIter, typename _Alloc, typename _TraitsT>
>>- inline void _Executor<_BiIter, _Alloc, _TraitsT>::
>>+ inline _StateIdT _Executor<_BiIter, _Alloc, _TraitsT>::
>> _M_handle_line_end_assertion(_Match_mode, _StateIdT __i)
>> {
>> const auto& __state = _M_nfa[__i];
>> if (_M_at_end())
>>- _M_frames.emplace_back(_S_fopcode_next, __state._M_next);
>>+ return __state._M_next;
>>+ return _S_invalid_state_id;
>> }
>>
>> template<typename _BiIter, typename _Alloc, typename _TraitsT>
>>- inline void _Executor<_BiIter, _Alloc, _TraitsT>::
>>+ inline _StateIdT _Executor<_BiIter, _Alloc, _TraitsT>::
>> _M_handle_word_boundary(_Match_mode, _StateIdT __i)
>> {
>> const auto& __state = _M_nfa[__i];
>> if (_M_word_boundary() == !__state._M_neg)
>>- _M_frames.emplace_back(_S_fopcode_next, __state._M_next);
>>+ return __state._M_next;
>>+ return _S_invalid_state_id;
>> }
>>
>> // Here __state._M_alt offers a single start node for a sub-NFA.
>> // We recursively invoke our algorithm to match the sub-NFA.
>> template<typename _BiIter, typename _Alloc, typename _TraitsT>
>>- void _Executor<_BiIter, _Alloc, _TraitsT>::
>>+ _StateIdT _Executor<_BiIter, _Alloc, _TraitsT>::
>> _M_handle_subexpr_lookahead(_Match_mode, _StateIdT __i)
>> {
>> const auto& __state = _M_nfa[__i];
>> if (_M_lookahead(__state._M_alt) == !__state._M_neg)
>>- _M_frames.emplace_back(_S_fopcode_next, __state._M_next);
>>+ return __state._M_next;
>>+ return _S_invalid_state_id;
>> }
>>
>> template<typename _BiIter, typename _Alloc, typename _TraitsT>
>>- void _Executor<_BiIter, _Alloc, _TraitsT>::
>>+#ifdef __OPTIMIZE__
>>+ [[__gnu__::__always_inline__]]
>>+#endif
>>+ inline _StateIdT _Executor<_BiIter, _Alloc, _TraitsT>::
>> _M_handle_match(_Match_mode, _StateIdT __i)
>> {
>> const auto& __state = _M_nfa[__i];
>> if (_M_current == _M_end)
>>- return;
>>+ return _S_invalid_state_id;
>> if (_M_search_mode == _Search_mode::_DFS)
>> {
>> if (__state._M_matches(*_M_current))
>> {
>> ++_M_current;
>>- _M_frames.emplace_back(_S_fopcode_next, __state._M_next);
>>+ return __state._M_next;
>> }
>> }
>> else
>> if (__state._M_matches(*_M_current))
>> _M_match_queue.emplace_back(__state._M_next, _M_cur_results);
>>+
>>+ return _S_invalid_state_id;
>> }
>>
>> template<typename _BiIter, typename _TraitsT>
>>@@ -462,7 +498,7 @@ namespace __detail
>> // (_M_current, _M_current + (__submatch.second - __submatch.first)).
>> // If matched, keep going; else just return and try another state.
>> template<typename _BiIter, typename _Alloc, typename _TraitsT>
>>- void _Executor<_BiIter, _Alloc, _TraitsT>::
>>+ _StateIdT _Executor<_BiIter, _Alloc, _TraitsT>::
>> _M_handle_backref(_Match_mode, _StateIdT __i)
>> {
>> __glibcxx_assert(_M_search_mode == _Search_mode::_DFS);
>>@@ -470,7 +506,7 @@ namespace __detail
>> const auto& __state = _M_nfa[__i];
>> auto& __submatch = _M_cur_results[__state._M_backref_index];
>> if (!__submatch.matched)
>>- return;
>>+ return _S_invalid_state_id;
>> auto __last = _M_current;
>> for (auto __tmp = __submatch.first;
>> __last != _M_end && __tmp != __submatch.second;
>>@@ -482,12 +518,17 @@ namespace __detail
>> __submatch.first, __submatch.second, _M_current, __last))
>> {
>> _M_current = __last;
>>- _M_frames.emplace_back(_S_fopcode_next, __state._M_next);
>>+ return __state._M_next;
>> }
>>+
>>+ return _S_invalid_state_id;
>> }
>>
>> template<typename _BiIter, typename _Alloc, typename _TraitsT>
>>- void _Executor<_BiIter, _Alloc, _TraitsT>::
>>+#ifdef __OPTIMIZE__
>>+ [[__gnu__::__always_inline__]]
>>+#endif
>>+ inline _StateIdT _Executor<_BiIter, _Alloc, _TraitsT>::
>> _M_handle_accept(_Match_mode __match_mode, _StateIdT)
>> {
>> if (_M_search_mode == _Search_mode::_DFS)
>>@@ -528,7 +569,7 @@ namespace __detail
>> {
>> if (_M_current == _M_begin
>> && (_M_flags & regex_constants::match_not_null))
>>- return;
>>+ return _S_invalid_state_id;
>> if (__match_mode == _Match_mode::_Prefix || _M_current == _M_end)
>> if (!_M_has_sol)
>> {
>>@@ -536,10 +577,14 @@ namespace __detail
>> _M_results = _M_cur_results;
>> }
>> }
>>+ return _S_invalid_state_id;
>> }
>>
>> template<typename _BiIter, typename _Alloc, typename _TraitsT>
>>- void _Executor<_BiIter, _Alloc, _TraitsT>::
>>+#ifdef __OPTIMIZE__
>>+ [[__gnu__::__always_inline__]]
>>+#endif
>>+ inline _StateIdT _Executor<_BiIter, _Alloc, _TraitsT>::
>> _M_handle_alternative(_Match_mode, _StateIdT __i)
>> {
>> const auto& __state = _M_nfa[__i];
>>@@ -549,7 +594,7 @@ namespace __detail
>> // Pick lhs if it matches. Only try rhs if it doesn't.
>> _M_frames.emplace_back(_S_fopcode_fallback_next, __state._M_next,
>> _M_current);
>>- _M_frames.emplace_back(_S_fopcode_next, __state._M_alt);
>>+ return __state._M_alt;
>> }
>> else
>> {
>>@@ -557,7 +602,7 @@ namespace __detail
>> // See "case _S_opcode_accept:" handling above.
>> _M_frames.emplace_back(_S_fopcode_posix_alternative, __state._M_next,
>> _M_current);
>>- _M_frames.emplace_back(_S_fopcode_next, __state._M_alt);
>>+ return __state._M_alt;
>> }
>> }
>>
>>@@ -565,54 +610,69 @@ namespace __detail
>>#ifdef __OPTIMIZE__
>> [[__gnu__::__always_inline__]]
>>#endif
>>- inline void _Executor<_BiIter, _Alloc, _TraitsT>::
>>+ inline _StateIdT _Executor<_BiIter, _Alloc, _TraitsT>::
>> _M_node(_Match_mode __match_mode, _StateIdT __i)
>> {
>>- if (_M_visited(__i))
>>- return;
>>+ // DFS has no _M_visited implementation as such don't even have the branch
>>+ // or the check in the call graph.
>>+ if (_M_search_mode == _Search_mode::_BFS)
>>+ if (_M_visited(__i))
>>+ return _S_invalid_state_id;
>>
>>+ _StateIdT __next = _S_invalid_state_id;
>> switch (_M_nfa[__i]._M_opcode())
>> {
>> case _S_opcode_repeat:
>>- _M_handle_repeat(__match_mode, __i); break;
>>+ __next = _M_handle_repeat(__match_mode, __i); break;
>> case _S_opcode_subexpr_begin:
>>- _M_handle_subexpr_begin(__match_mode, __i); break;
>>+ __next = _M_handle_subexpr_begin(__match_mode, __i);
>>+ break;
>> case _S_opcode_subexpr_end:
>>- _M_handle_subexpr_end(__match_mode, __i); break;
>>+ __next = _M_handle_subexpr_end(__match_mode, __i);
>>+ break;
>> case _S_opcode_line_begin_assertion:
>>- _M_handle_line_begin_assertion(__match_mode, __i); break;
>>+ __next = _M_handle_line_begin_assertion(__match_mode, __i); break;
>> case _S_opcode_line_end_assertion:
>>- _M_handle_line_end_assertion(__match_mode, __i); break;
>>+ __next = _M_handle_line_end_assertion(__match_mode, __i); break;
>> case _S_opcode_word_boundary:
>>- _M_handle_word_boundary(__match_mode, __i); break;
>>+ __next = _M_handle_word_boundary(__match_mode, __i); break;
>> case _S_opcode_subexpr_lookahead:
>>- _M_handle_subexpr_lookahead(__match_mode, __i); break;
>>+ __next = _M_handle_subexpr_lookahead(__match_mode, __i); break;
>> case _S_opcode_match:
>>- _M_handle_match(__match_mode, __i); break;
>>+ __next = _M_handle_match(__match_mode, __i); break;
>> case _S_opcode_backref:
>> if (_M_search_mode == _Search_mode::_DFS)
>>- _M_handle_backref(__match_mode, __i);
>>+ __next = _M_handle_backref(__match_mode, __i);
>> else
>> __builtin_unreachable();
>> break;
>> case _S_opcode_accept:
>>- _M_handle_accept(__match_mode, __i); break;
>>+ __next = _M_handle_accept(__match_mode, __i); break;
>> case _S_opcode_alternative:
>>- _M_handle_alternative(__match_mode, __i); break;
>>+ __next = _M_handle_alternative(__match_mode, __i); break;
>> default:
>> __glibcxx_assert(false);
>> }
>>+ return __next;
>> }
>>
>> template<typename _BiIter, typename _Alloc, typename _TraitsT>
>> void _Executor<_BiIter, _Alloc, _TraitsT>::
>> _M_dfs(_Match_mode __match_mode, _StateIdT __start)
>> {
>>- const bool __dfs_mode = (_M_search_mode == _Search_mode::_DFS);
>>- _M_frames.emplace_back(_S_fopcode_next, __start);
>>+ _StateIdT __next = __start;
>>
>>- while (!_M_frames.empty())
>>+ while (true)
>> {
>>+ // Follow immediate successors without re-entering the frame
>>+ // loop until we fail. This avoids the needless state save and
>>+ // restore through memory.
>>+ while (__next != _S_invalid_state_id)
>>+ __next = _M_node(__match_mode, __next);
>>+
>>+ if (_M_frames.empty())
>>+ break;
>>+
>> _ExecutorFrame<_BiIter> __frame = std::move(_M_frames.back());
>> _M_frames.pop_back();
>>
>>@@ -621,27 +681,27 @@ namespace __detail
>> case _S_fopcode_fallback_next:
>> if (_M_has_sol)
>> break;
>>- if (__dfs_mode)
>>+ if (_M_search_mode == _Search_mode::_DFS)
>> _M_current = __frame._M_pos;
>> [[__fallthrough__]];
>> case _S_fopcode_next:
>>- _M_node(__match_mode, __frame._M_state_id);
>>+ __next = __frame._M_state_id;
>> break;
>>
>> case _S_fopcode_fallback_rep_once_more:
>> if (_M_has_sol)
>> break;
>>- if (__dfs_mode)
>>+ if (_M_search_mode == _Search_mode::_DFS)
>> _M_current = __frame._M_pos;
>> [[__fallthrough__]];
>> case _S_fopcode_rep_once_more:
>>- _M_rep_once_more(__match_mode, __frame._M_state_id);
>>+ __next = _M_rep_once_more(__match_mode, __frame._M_state_id);
>> break;
>>
>> case _S_fopcode_posix_alternative:
>> _M_frames.emplace_back(_S_fopcode_merge_sol, 0, _M_has_sol);
>>- _M_frames.emplace_back(_S_fopcode_next, __frame._M_state_id);
>>- if (__dfs_mode)
>>+ __next = __frame._M_state_id;
>>+ if (_M_search_mode == _Search_mode::_DFS)
>> _M_current = __frame._M_pos;
>> _M_has_sol = false;
>> break;
>>@@ -695,6 +755,7 @@ namespace __detail
>>
>> return __left_is_word != __right_is_word;
>> }
>>+_GLIBCXX_END_INLINE_ABI_NAMESPACE(_V2)
>>} // namespace __detail
>>#pragma GCC diagnostic pop
>>
>>
>>
>>--
More information about the Libstdc++
mailing list