From 45f9b04b6dbd8f2a276e9251dac599718ad1e725 Mon Sep 17 00:00:00 2001 From: Youmu Liu <105042599+Nitload-NSI@users.noreply.github.com> Date: Wed, 24 Jun 2026 18:09:05 +0800 Subject: [PATCH] add more ir opt code, clearified what to opt, so current opt code may need rewrite --- StgSharp.RegularAnalysis/Text/RegexIR.cs | 5 +- .../Text/RegexInterpreter.IROptimize.cs | 117 ++++++++++++++---- StgSharpDebug/TestRegexAnalyse.cs | 2 +- future/ngra-regex-ir.md | 51 +++----- 4 files changed, 121 insertions(+), 54 deletions(-) diff --git a/StgSharp.RegularAnalysis/Text/RegexIR.cs b/StgSharp.RegularAnalysis/Text/RegexIR.cs index 7dc15ec3..446257f4 100644 --- a/StgSharp.RegularAnalysis/Text/RegexIR.cs +++ b/StgSharp.RegularAnalysis/Text/RegexIR.cs @@ -62,10 +62,13 @@ namespace StgSharp.RegularAnalysis.Text } internal sealed class RegexFindSeqIR( - string pattern + string pattern, + bool isFirst ) : RegexIR { + public bool IsFirst { get; } = isFirst; + public string Pattern { get; } = pattern; internal override RegexIRCommand Command => RegexIRCommand.Find_Seq; diff --git a/StgSharp.RegularAnalysis/Text/RegexInterpreter.IROptimize.cs b/StgSharp.RegularAnalysis/Text/RegexInterpreter.IROptimize.cs index 804e1e0f..95b7bd63 100644 --- a/StgSharp.RegularAnalysis/Text/RegexInterpreter.IROptimize.cs +++ b/StgSharp.RegularAnalysis/Text/RegexInterpreter.IROptimize.cs @@ -28,6 +28,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; @@ -36,19 +37,22 @@ namespace StgSharp.RegularAnalysis.Text public partial class RegexAnalyzer { - internal static RegexInfo ScanAndOptimizeIR( - List baseIR, - out List optIR + // this array is not thread safe so opt process must be done in single thread. + private RegexIR[] _opt_buffer = new RegexIR[16]; + + internal RegexInfo ScanAndOptimizeIR( + List baseIR, + out List optIR ) { int index = 0; + Span origin_span = CollectionsMarshal.AsSpan(baseIR); List optimizedIR = new(baseIR.Count); List segment = []; RegexInfo info = new(); do { - // TODO scan and opt here - OptimizeIR(baseIR, ref index, segment, info); + OptimizeIR(origin_span, ref index, segment, info); optimizedIR.AddRange(segment); index++; } while (index < baseIR.Count); @@ -56,37 +60,106 @@ namespace StgSharp.RegularAnalysis.Text return info; } - private static void OptimizeDot( - RegexCountSetIR root, - List ir, - ref int index, - List optimizedIR, - RegexInfo info + private int ExpandCount( + string pattern, + int min, + int max, + bool is_greedy, + in Span span + ) + { + // expand count operation to less cycles. + + const int fast_seq_length = 32; + + int fast_count = fast_seq_length / pattern.Length; + fast_count = int.Min(fast_count, min); + if (fast_count > 0) + { + string fast_pattern = string.Concat(Enumerable.Repeat(pattern, fast_count)); + + int fast_min = min / fast_count; + int consumed_count = fast_min * fast_count; + int remain_min = min - consumed_count; + int remain_max = max - consumed_count; + remain_max = int.Max(-1, remain_max); //for negative stands for infinitive + if (remain_max == 0) + { + // a{2} + RegexCountSeqIR fast = new(fast_min, fast_min, is_greedy, fast_pattern); + span[0] = fast; + return 1; + } else + { + // a{2,5} + RegexCountSeqIR fast = new(fast_min, fast_min, is_greedy, fast_pattern); + RegexCountSeqIR tail = new(remain_min, remain_max, is_greedy, pattern); + span[0] = fast; + span[1] = tail; + return 2; + } + } else if (fast_count == 0) + { + // least count is 0 + span[0] = new RegexCountSeqIR(0, max, is_greedy, pattern); + return 1; + } else + { + // least count is infinitive + span[0] = new RegexCountSeqIR(-1, -1, is_greedy, pattern); + return 1; + } + } + + private void OptimizeDot( + RegexCountSetIR root, + Span ir, + ref int index, + List optimizedIR, + RegexInfo info ) { int next = index + 1; - if (next >= ir.Count) + optimizedIR.Clear(); + if (next >= ir.Length) { - optimizedIR.Clear(); optimizedIR.Add(root); return; } RegexIR nextIR = ir[next]; - if (nextIR.Command == RegexIRCommand.Match_Seq) + if (nextIR is RegexMatchSeqIR match_seq) { // opt .*abc style - RegexFindSeqIR find = new((nextIR as RegexMatchSeqIR)!.Pattern); + RegexFindSeqIR find = new(match_seq.Pattern); optimizedIR.Add(find); info.MinLength += find.Pattern.Length; return; - } else { } + } else if (nextIR is RegexCountSeqIR count_seq) + { + // opt .* (abc)* or .*(abc){2,5} style + string pattern = count_seq.Sequence; + int min = count_seq.Min; + int max = count_seq.Max; + switch ((min, max)) + { + case (0, _): + + break; + default: + RegexFindSeqIR find = new(pattern); + + // RegexCountSeqIR + + break; + } + } } - private static void OptimizeIR( - List ir, - ref int index, - List optimizedIR, - RegexInfo info + private void OptimizeIR( + Span ir, + ref int index, + List optimizedIR, + RegexInfo info ) { optimizedIR.Clear(); @@ -120,6 +193,8 @@ namespace StgSharp.RegularAnalysis.Text public int TryDepth { get; set; } + public int GroupCount { get; set; } + } } diff --git a/StgSharpDebug/TestRegexAnalyse.cs b/StgSharpDebug/TestRegexAnalyse.cs index 4e52d4a2..907efc05 100644 --- a/StgSharpDebug/TestRegexAnalyse.cs +++ b/StgSharpDebug/TestRegexAnalyse.cs @@ -72,7 +72,7 @@ namespace StgSharp } } - [GeneratedRegex(@"a*.[abc]*(abcd|a(bc)c)+(?abc)", RegexOptions.ExplicitCapture)] + [GeneratedRegex(@".*a*b", RegexOptions.ExplicitCapture)] internal static partial Regex GetRegex(); } diff --git a/future/ngra-regex-ir.md b/future/ngra-regex-ir.md index 4d50de5e..f3ed80d9 100644 --- a/future/ngra-regex-ir.md +++ b/future/ngra-regex-ir.md @@ -33,43 +33,32 @@ 1. 尽可能降低源生成或者运行时执行中,对多指令上下文的依赖,尽可能保证每一个IR可以单独输出最佳的代码 2. 减少多余的结构,使得一行指令尽可能处理多的数据 -### 语义改写优化 +### AST优化器 -1. 计数器展开 -建议在lower做 -对于形如a+b或者a{2,4}b的正则表达式,可以优化为a*(ab|b)和a{2,3}(ab|b)的写法 +1. ALT合并 +将多个ALT CASE合并到一个链表中 -2. 确定性计数展开 -建议在lower做 -对于形如a{m,n}的正则表达式,将其展开为a{m}a{0,n-m}的形式 +2. SPAN合并 +将连续的CHAR CONCAT合并为一个char span -3. charset合并 -建议在ast-opt做 -对于形如[ abc ]|[ def ]的表达式,将其合并为[ abcdef ] +3. alt合并 +将多个alt分支中的公共部分提取出来 -4. charset展开 -在lower做 -对于形如abc[ de ]的表达式,如果字符集容量不超过4,可以站卡为(abcd|abce)形式 +### IR优化器 -### 匹配策略改变 +1. .*a特征,优化为 +FIND_SEQ(a, last) +.*?a特征,优化为 +FIND_SEQ(a, first) -1. 长度预测 -在source-gen阶段做 -对于一个正则表达式,可以计算其所需的最小长度,可以在开始的时候提前进行比较 +2. a+特征,优化为 +MATCH_SEQ(a) +COUNT_SEQ(a,0,+inf,greedy) -2. .*优化 -在lower阶段做 -对于COUNT_SET min=0 max=-1 "."指令,也就是.*操作,将后续指令从match转为find +3. a{1} -> a -3. [ set ]*优化 -在source-gen阶段做 -对于其他的COUNT_SET min=0 max=-1 "???"指令,使用IndexOf,IndexOfAnyExcept等进行优化 +4. .\*a\*特征,优化为.* -4. 首字母检查 -**这个不好做,因为IR直接把ALT打碎成if-else串了** -对于(abc|defg|xyz)表达式,可以检查其首字母 - -### C# span opt trick - -1. Span.StartWith -2. [ 0-9 ]展开为IndexOf("0123456789") \ No newline at end of file +5. a{m,n}特征,展开为 +MATCH_SEQ(aa...m个a....aa) +COUNT_SEQ(a,0,n,greedy)