From 60b297118043eef54eb924d81f940e79c0316433 Mon Sep 17 00:00:00 2001 From: Russ Cox <rsc@golang.org> Date: Tue, 2 Oct 2018 09:29:47 -0400 Subject: [PATCH] regexp: split one-pass execution out of machine struct MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows the one-pass executions to have their own pool of (much smaller) allocated structures. A step toward eliminating the per-Regexp machine cache. Not much effect on benchmarks, since there are no optimizations here, and pools are a tiny bit slower than a locked data structure for single-threaded code. name old time/op new time/op delta Find-12 254ns 卤 0% 252ns 卤 0% -0.94% (p=0.000 n=9+10) FindAllNoMatches-12 135ns 卤 0% 134ns 卤 1% -0.49% (p=0.002 n=9+9) FindString-12 247ns 卤 0% 246ns 卤 0% -0.24% (p=0.003 n=8+10) FindSubmatch-12 334ns 卤 0% 333ns 卤 2% ~ (p=0.283 n=10+10) FindStringSubmatch-12 321ns 卤 0% 320ns 卤 0% -0.51% (p=0.000 n=9+10) Literal-12 92.2ns 卤 0% 91.1ns 卤 0% -1.25% (p=0.000 n=9+10) NotLiteral-12 1.47碌s 卤 0% 1.45碌s 卤 0% -0.99% (p=0.000 n=9+10) MatchClass-12 2.17碌s 卤 0% 2.19碌s 卤 0% +0.84% (p=0.000 n=7+9) MatchClass_InRange-12 2.13碌s 卤 0% 2.09碌s 卤 0% -1.70% (p=0.000 n=10+10) ReplaceAll-12 1.39碌s 卤 0% 1.39碌s 卤 0% +0.51% (p=0.000 n=10+10) AnchoredLiteralShortNonMatch-12 83.2ns 卤 0% 82.4ns 卤 0% -0.96% (p=0.000 n=8+8) AnchoredLiteralLongNonMatch-12 105ns 卤 0% 106ns 卤 1% ~ (p=0.087 n=10+10) AnchoredShortMatch-12 131ns 卤 0% 130ns 卤 0% -0.76% (p=0.000 n=10+9) AnchoredLongMatch-12 267ns 卤 0% 272ns 卤 0% +2.01% (p=0.000 n=10+8) OnePassShortA-12 611ns 卤 0% 615ns 卤 0% +0.61% (p=0.000 n=9+10) NotOnePassShortA-12 552ns 卤 0% 549ns 卤 0% -0.46% (p=0.000 n=8+9) OnePassShortB-12 491ns 卤 0% 494ns 卤 0% +0.61% (p=0.000 n=8+8) NotOnePassShortB-12 412ns 卤 0% 412ns 卤 1% ~ (p=0.151 n=9+10) OnePassLongPrefix-12 112ns 卤 0% 108ns 卤 0% -3.57% (p=0.000 n=10+10) OnePassLongNotPrefix-12 410ns 卤 0% 402ns 卤 0% -1.95% (p=0.000 n=9+8) MatchParallelShared-12 38.8ns 卤 1% 38.6ns 卤 2% ~ (p=0.536 n=10+9) MatchParallelCopied-12 39.2ns 卤 3% 39.4ns 卤 7% ~ (p=0.986 n=10+10) QuoteMetaAll-12 94.6ns 卤 0% 94.9ns 卤 0% +0.29% (p=0.001 n=8+9) QuoteMetaNone-12 52.7ns 卤 0% 52.7ns 卤 0% ~ (all equal) Match/Easy0/32-12 72.9ns 卤 0% 72.1ns 卤 0% -1.07% (p=0.000 n=9+9) Match/Easy0/1K-12 298ns 卤 0% 298ns 卤 0% ~ (p=0.140 n=6+8) Match/Easy0/32K-12 4.60碌s 卤 2% 4.64碌s 卤 1% ~ (p=0.171 n=10+10) Match/Easy0/1M-12 235碌s 卤 0% 234碌s 卤 0% -0.14% (p=0.004 n=10+10) Match/Easy0/32M-12 7.96ms 卤 0% 7.95ms 卤 0% -0.12% (p=0.043 n=10+9) Match/Easy0i/32-12 1.09碌s 卤 0% 1.10碌s 卤 0% +0.15% (p=0.000 n=8+9) Match/Easy0i/1K-12 31.7碌s 卤 0% 31.8碌s 卤 1% ~ (p=0.905 n=9+10) Match/Easy0i/32K-12 1.61ms 卤 0% 1.62ms 卤 1% +1.12% (p=0.000 n=9+10) Match/Easy0i/1M-12 51.4ms 卤 0% 51.8ms 卤 0% +0.85% (p=0.000 n=8+8) Match/Easy0i/32M-12 1.65s 卤 1% 1.65s 卤 0% ~ (p=0.113 n=9+9) Match/Easy1/32-12 67.9ns 卤 0% 67.7ns 卤 1% ~ (p=0.232 n=8+10) Match/Easy1/1K-12 884ns 卤 0% 873ns 卤 0% -1.29% (p=0.000 n=9+10) Match/Easy1/32K-12 39.2碌s 卤 0% 39.4碌s 卤 0% +0.50% (p=0.000 n=9+10) Match/Easy1/1M-12 1.39ms 卤 0% 1.39ms 卤 0% +0.29% (p=0.000 n=9+10) Match/Easy1/32M-12 44.2ms 卤 1% 44.3ms 卤 0% +0.21% (p=0.029 n=10+10) Match/Medium/32-12 1.05碌s 卤 0% 1.04碌s 卤 0% -0.27% (p=0.001 n=8+9) Match/Medium/1K-12 31.3碌s 卤 0% 31.4碌s 卤 0% +0.39% (p=0.000 n=9+8) Match/Medium/32K-12 1.45ms 卤 0% 1.45ms 卤 0% +0.33% (p=0.000 n=8+9) Match/Medium/1M-12 46.2ms 卤 0% 46.4ms 卤 0% +0.35% (p=0.000 n=9+8) Match/Medium/32M-12 1.48s 卤 0% 1.49s 卤 1% +0.70% (p=0.000 n=8+10) Match/Hard/32-12 1.49碌s 卤 0% 1.48碌s 卤 0% -0.43% (p=0.000 n=10+9) Match/Hard/1K-12 45.1碌s 卤 1% 45.0碌s 卤 1% ~ (p=0.393 n=10+10) Match/Hard/32K-12 2.18ms 卤 1% 2.24ms 卤 0% +2.71% (p=0.000 n=9+8) Match/Hard/1M-12 69.7ms 卤 1% 71.6ms 卤 0% +2.76% (p=0.000 n=9+7) Match/Hard/32M-12 2.23s 卤 1% 2.29s 卤 0% +2.65% (p=0.000 n=9+9) Match/Hard1/32-12 7.89碌s 卤 0% 7.89碌s 卤 0% ~ (p=0.286 n=9+9) Match/Hard1/1K-12 244碌s 卤 0% 244碌s 卤 0% ~ (p=0.905 n=9+10) Match/Hard1/32K-12 10.3ms 卤 0% 10.3ms 卤 0% ~ (p=0.796 n=10+10) Match/Hard1/1M-12 331ms 卤 0% 331ms 卤 0% ~ (p=0.167 n=8+9) Match/Hard1/32M-12 10.6s 卤 0% 10.6s 卤 0% ~ (p=0.315 n=8+10) Match_onepass_regex/32-12 812ns 卤 0% 830ns 卤 0% +2.19% (p=0.000 n=10+9) Match_onepass_regex/1K-12 28.5碌s 卤 0% 28.7碌s 卤 1% +0.97% (p=0.000 n=10+9) Match_onepass_regex/32K-12 936碌s 卤 0% 949碌s 卤 0% +1.43% (p=0.000 n=10+8) Match_onepass_regex/1M-12 30.2ms 卤 0% 30.4ms 卤 0% +0.62% (p=0.000 n=10+8) Match_onepass_regex/32M-12 970ms 卤 0% 973ms 卤 0% +0.35% (p=0.000 n=10+9) CompileOnepass-12 4.63碌s 卤 1% 4.64碌s 卤 0% ~ (p=0.060 n=10+10) [Geo mean] 23.3碌s 23.3碌s +0.12% https://perf.golang.org/search?q=upload:20181004.2 Change-Id: Iff9e9f9d4a4698162126a2f300e8ed1b1a39361e Reviewed-on: https://go-review.googlesource.com/c/139780 Run-TryBot: Russ Cox <rsc@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> --- src/regexp/all_test.go | 4 +- src/regexp/exec.go | 113 ++++++++++++++++++++++++------------- src/regexp/exec_test.go | 2 +- src/regexp/onepass.go | 24 ++++---- src/regexp/onepass_test.go | 86 ++++++++++++++-------------- src/regexp/regexp.go | 4 +- 6 files changed, 131 insertions(+), 102 deletions(-) diff --git a/src/regexp/all_test.go b/src/regexp/all_test.go index 0fabeae59f..8cbc2962cb 100644 --- a/src/regexp/all_test.go +++ b/src/regexp/all_test.go @@ -550,8 +550,8 @@ func TestOnePassCutoff(t *testing.T) { if err != nil { t.Fatalf("compile: %v", err) } - if compileOnePass(p) != notOnePass { - t.Fatalf("makeOnePass succeeded; wanted notOnePass") + if compileOnePass(p) != nil { + t.Fatalf("makeOnePass succeeded; wanted nil") } } diff --git a/src/regexp/exec.go b/src/regexp/exec.go index 271174670e..23908d22d5 100644 --- a/src/regexp/exec.go +++ b/src/regexp/exec.go @@ -7,6 +7,7 @@ package regexp import ( "io" "regexp/syntax" + "sync" ) // A queue is a 'sparse array' holding pending threads of execution. @@ -37,7 +38,6 @@ type thread struct { type machine struct { re *Regexp // corresponding Regexp p *syntax.Prog // compiled program - op *onePassProg // compiled onepass program, or notOnePass q0, q1 queue // two queues for runq, nextq pool []*thread // pool of available threads matched bool // whether a match was found @@ -93,8 +93,8 @@ func (i *inputs) init(r io.RuneReader, b []byte, s string) (input, int) { } // progMachine returns a new machine running the prog p. -func progMachine(p *syntax.Prog, op *onePassProg) *machine { - m := &machine{p: p, op: op} +func progMachine(p *syntax.Prog) *machine { + m := &machine{p: p} n := len(m.p.Inst) m.q0 = queue{make([]uint32, n), make([]entry, 0, n)} m.q1 = queue{make([]uint32, n), make([]entry, 0, n)} @@ -327,20 +327,47 @@ func (m *machine) add(q *queue, pc uint32, pos int, cap []int, cond syntax.Empty return t } -// onepass runs the machine over the input starting at pos. -// It reports whether a match was found. -// If so, m.matchcap holds the submatch information. -// ncap is the number of captures. -func (m *machine) onepass(i input, pos, ncap int) bool { - startCond := m.re.cond +type onePassMachine struct { + inputs inputs + matchcap []int +} + +var onePassPool sync.Pool + +func newOnePassMachine() *onePassMachine { + m, ok := onePassPool.Get().(*onePassMachine) + if !ok { + m = new(onePassMachine) + } + return m +} + +func freeOnePassMachine(m *onePassMachine) { + m.inputs.clear() + onePassPool.Put(m) +} + +// doOnePass implements r.doExecute using the one-pass execution engine. +func (re *Regexp) doOnePass(ir io.RuneReader, ib []byte, is string, pos, ncap int, dstCap []int) []int { + startCond := re.cond if startCond == ^syntax.EmptyOp(0) { // impossible - return false + return nil } - m.matched = false - m.matchcap = m.matchcap[:ncap] + + m := newOnePassMachine() + if cap(m.matchcap) < ncap { + m.matchcap = make([]int, ncap) + } else { + m.matchcap = m.matchcap[:ncap] + } + + matched := false for i := range m.matchcap { m.matchcap[i] = -1 } + + i, _ := m.inputs.init(ir, ib, is) + r, r1 := endOfText, endOfText width, width1 := 0, 0 r, width = i.step(pos) @@ -353,59 +380,59 @@ func (m *machine) onepass(i input, pos, ncap int) bool { } else { flag = i.context(pos) } - pc := m.op.Start - inst := m.op.Inst[pc] + pc := re.onepass.Start + inst := re.onepass.Inst[pc] // If there is a simple literal prefix, skip over it. if pos == 0 && syntax.EmptyOp(inst.Arg)&^flag == 0 && - len(m.re.prefix) > 0 && i.canCheckPrefix() { + len(re.prefix) > 0 && i.canCheckPrefix() { // Match requires literal prefix; fast search for it. - if !i.hasPrefix(m.re) { - return m.matched + if !i.hasPrefix(re) { + goto Return } - pos += len(m.re.prefix) + pos += len(re.prefix) r, width = i.step(pos) r1, width1 = i.step(pos + width) flag = i.context(pos) - pc = int(m.re.prefixEnd) + pc = int(re.prefixEnd) } for { - inst = m.op.Inst[pc] + inst = re.onepass.Inst[pc] pc = int(inst.Out) switch inst.Op { default: panic("bad inst") case syntax.InstMatch: - m.matched = true + matched = true if len(m.matchcap) > 0 { m.matchcap[0] = 0 m.matchcap[1] = pos } - return m.matched + goto Return case syntax.InstRune: if !inst.MatchRune(r) { - return m.matched + goto Return } case syntax.InstRune1: if r != inst.Rune[0] { - return m.matched + goto Return } case syntax.InstRuneAny: // Nothing case syntax.InstRuneAnyNotNL: if r == '\n' { - return m.matched + goto Return } // peek at the input rune to see which branch of the Alt to take case syntax.InstAlt, syntax.InstAltMatch: pc = int(onePassNext(&inst, r)) continue case syntax.InstFail: - return m.matched + goto Return case syntax.InstNop: continue case syntax.InstEmptyWidth: if syntax.EmptyOp(inst.Arg)&^flag != 0 { - return m.matched + goto Return } continue case syntax.InstCapture: @@ -424,7 +451,16 @@ func (m *machine) onepass(i input, pos, ncap int) bool { r1, width1 = i.step(pos + width) } } - return m.matched + +Return: + if !matched { + freeOnePassMachine(m) + return nil + } + + dstCap = append(dstCap, m.matchcap...) + freeOnePassMachine(m) + return dstCap } // doMatch reports whether either r, b or s match the regexp. @@ -442,25 +478,22 @@ func (re *Regexp) doExecute(r io.RuneReader, b []byte, s string, pos int, ncap i dstCap = arrayNoInts[:0:0] } - if re.onepass == notOnePass && r == nil && len(b)+len(s) < re.maxBitStateLen { + if re.onepass != nil { + return re.doOnePass(r, b, s, pos, ncap, dstCap) + } + if r == nil && len(b)+len(s) < re.maxBitStateLen { return re.backtrack(b, s, pos, ncap, dstCap) } m := re.get() i, _ := m.inputs.init(r, b, s) - if m.op != notOnePass { - if !m.onepass(i, pos, ncap) { - re.put(m) - return nil - } - } else { - m.init(ncap) - if !m.match(i, pos) { - re.put(m) - return nil - } + m.init(ncap) + if !m.match(i, pos) { + re.put(m) + return nil } + dstCap = append(dstCap, m.matchcap...) re.put(m) return dstCap diff --git a/src/regexp/exec_test.go b/src/regexp/exec_test.go index 02258e6e74..1489219328 100644 --- a/src/regexp/exec_test.go +++ b/src/regexp/exec_test.go @@ -684,7 +684,7 @@ func BenchmarkMatch(b *testing.B) { func BenchmarkMatch_onepass_regex(b *testing.B) { isRaceBuilder := strings.HasSuffix(testenv.Builder(), "-race") r := MustCompile(`(?s)\A.*\z`) - if r.get().op == notOnePass { + if r.onepass == nil { b.Fatalf("want onepass regex, but %q is not onepass", r) } for _, size := range benchSizes { diff --git a/src/regexp/onepass.go b/src/regexp/onepass.go index 125be59a7d..2f3ce6f9f6 100644 --- a/src/regexp/onepass.go +++ b/src/regexp/onepass.go @@ -294,12 +294,12 @@ var anyRune = []rune{0, unicode.MaxRune} // makeOnePass creates a onepass Prog, if possible. It is possible if at any alt, // the match engine can always tell which branch to take. The routine may modify // p if it is turned into a onepass Prog. If it isn't possible for this to be a -// onepass Prog, the Prog notOnePass is returned. makeOnePass is recursive +// onepass Prog, the Prog nil is returned. makeOnePass is recursive // to the size of the Prog. func makeOnePass(p *onePassProg) *onePassProg { // If the machine is very long, it's not worth the time to check if we can use one pass. if len(p.Inst) >= 1000 { - return notOnePass + return nil } var ( @@ -446,11 +446,11 @@ func makeOnePass(p *onePassProg) *onePassProg { visitQueue.clear() pc := instQueue.next() if !check(pc, m) { - p = notOnePass + p = nil break } } - if p != notOnePass { + if p != nil { for i := range p.Inst { p.Inst[i].Rune = onePassRunes[i] } @@ -458,20 +458,18 @@ func makeOnePass(p *onePassProg) *onePassProg { return p } -var notOnePass *onePassProg = nil - // compileOnePass returns a new *syntax.Prog suitable for onePass execution if the original Prog -// can be recharacterized as a one-pass regexp program, or syntax.notOnePass if the +// can be recharacterized as a one-pass regexp program, or syntax.nil if the // Prog cannot be converted. For a one pass prog, the fundamental condition that must // be true is: at any InstAlt, there must be no ambiguity about what branch to take. func compileOnePass(prog *syntax.Prog) (p *onePassProg) { if prog.Start == 0 { - return notOnePass + return nil } // onepass regexp is anchored if prog.Inst[prog.Start].Op != syntax.InstEmptyWidth || syntax.EmptyOp(prog.Inst[prog.Start].Arg)&syntax.EmptyBeginText != syntax.EmptyBeginText { - return notOnePass + return nil } // every instruction leading to InstMatch must be EmptyEndText for _, inst := range prog.Inst { @@ -479,18 +477,18 @@ func compileOnePass(prog *syntax.Prog) (p *onePassProg) { switch inst.Op { default: if opOut == syntax.InstMatch { - return notOnePass + return nil } case syntax.InstAlt, syntax.InstAltMatch: if opOut == syntax.InstMatch || prog.Inst[inst.Arg].Op == syntax.InstMatch { - return notOnePass + return nil } case syntax.InstEmptyWidth: if opOut == syntax.InstMatch { if syntax.EmptyOp(inst.Arg)&syntax.EmptyEndText == syntax.EmptyEndText { continue } - return notOnePass + return nil } } } @@ -501,7 +499,7 @@ func compileOnePass(prog *syntax.Prog) (p *onePassProg) { // checkAmbiguity on InstAlts, build onepass Prog if possible p = makeOnePass(p) - if p != notOnePass { + if p != nil { cleanupOnePass(p, prog) } return p diff --git a/src/regexp/onepass_test.go b/src/regexp/onepass_test.go index 6b622ac356..a0f2e39048 100644 --- a/src/regexp/onepass_test.go +++ b/src/regexp/onepass_test.go @@ -134,47 +134,45 @@ func TestMergeRuneSet(t *testing.T) { } } -var onePass = &onePassProg{} - var onePassTests = []struct { - re string - onePass *onePassProg + re string + isOnePass bool }{ - {`^(?:a|(?:a*))$`, notOnePass}, - {`^(?:(a)|(?:a*))$`, notOnePass}, - {`^(?:(?:(?:.(?:$))?))$`, onePass}, - {`^abcd$`, onePass}, - {`^(?:(?:a{0,})*?)$`, onePass}, - {`^(?:(?:a+)*)$`, onePass}, - {`^(?:(?:a|(?:aa)))$`, onePass}, - {`^(?:[^\s\S])$`, onePass}, - {`^(?:(?:a{3,4}){0,})$`, notOnePass}, - {`^(?:(?:(?:a*)+))$`, onePass}, - {`^[a-c]+$`, onePass}, - {`^[a-c]*$`, onePass}, - {`^(?:a*)$`, onePass}, - {`^(?:(?:aa)|a)$`, onePass}, - {`^[a-c]*`, notOnePass}, - {`^...$`, onePass}, - {`^(?:a|(?:aa))$`, onePass}, - {`^a((b))c$`, onePass}, - {`^a.[l-nA-Cg-j]?e$`, onePass}, - {`^a((b))$`, onePass}, - {`^a(?:(b)|(c))c$`, onePass}, - {`^a(?:(b*)|(c))c$`, notOnePass}, - {`^a(?:b|c)$`, onePass}, - {`^a(?:b?|c)$`, onePass}, - {`^a(?:b?|c?)$`, notOnePass}, - {`^a(?:b?|c+)$`, onePass}, - {`^a(?:b+|(bc))d$`, notOnePass}, - {`^a(?:bc)+$`, onePass}, - {`^a(?:[bcd])+$`, onePass}, - {`^a((?:[bcd])+)$`, onePass}, - {`^a(:?b|c)*d$`, onePass}, - {`^.bc(d|e)*$`, onePass}, - {`^(?:(?:aa)|.)$`, notOnePass}, - {`^(?:(?:a{1,2}){1,2})$`, notOnePass}, - {`^l` + strings.Repeat("o", 2<<8) + `ng$`, onePass}, + {`^(?:a|(?:a*))$`, false}, + {`^(?:(a)|(?:a*))$`, false}, + {`^(?:(?:(?:.(?:$))?))$`, true}, + {`^abcd$`, true}, + {`^(?:(?:a{0,})*?)$`, true}, + {`^(?:(?:a+)*)$`, true}, + {`^(?:(?:a|(?:aa)))$`, true}, + {`^(?:[^\s\S])$`, true}, + {`^(?:(?:a{3,4}){0,})$`, false}, + {`^(?:(?:(?:a*)+))$`, true}, + {`^[a-c]+$`, true}, + {`^[a-c]*$`, true}, + {`^(?:a*)$`, true}, + {`^(?:(?:aa)|a)$`, true}, + {`^[a-c]*`, false}, + {`^...$`, true}, + {`^(?:a|(?:aa))$`, true}, + {`^a((b))c$`, true}, + {`^a.[l-nA-Cg-j]?e$`, true}, + {`^a((b))$`, true}, + {`^a(?:(b)|(c))c$`, true}, + {`^a(?:(b*)|(c))c$`, false}, + {`^a(?:b|c)$`, true}, + {`^a(?:b?|c)$`, true}, + {`^a(?:b?|c?)$`, false}, + {`^a(?:b?|c+)$`, true}, + {`^a(?:b+|(bc))d$`, false}, + {`^a(?:bc)+$`, true}, + {`^a(?:[bcd])+$`, true}, + {`^a((?:[bcd])+)$`, true}, + {`^a(:?b|c)*d$`, true}, + {`^.bc(d|e)*$`, true}, + {`^(?:(?:aa)|.)$`, false}, + {`^(?:(?:a{1,2}){1,2})$`, false}, + {`^l` + strings.Repeat("o", 2<<8) + `ng$`, true}, } func TestCompileOnePass(t *testing.T) { @@ -194,9 +192,9 @@ func TestCompileOnePass(t *testing.T) { t.Errorf("Compile(%q) got err:%s, want success", test.re, err) continue } - onePass = compileOnePass(p) - if (onePass == notOnePass) != (test.onePass == notOnePass) { - t.Errorf("CompileOnePass(%q) got %v, expected %v", test.re, onePass, test.onePass) + isOnePass := compileOnePass(p) != nil + if isOnePass != test.isOnePass { + t.Errorf("CompileOnePass(%q) got isOnePass=%v, expected %v", test.re, isOnePass, test.isOnePass) } } } @@ -216,8 +214,8 @@ func TestRunOnePass(t *testing.T) { t.Errorf("Compile(%q): got err: %s", test.re, err) continue } - if re.onepass == notOnePass { - t.Errorf("Compile(%q): got notOnePass, want one-pass", test.re) + if re.onepass == nil { + t.Errorf("Compile(%q): got nil, want one-pass", test.re) continue } if !re.MatchString(test.match) { diff --git a/src/regexp/regexp.go b/src/regexp/regexp.go index dafcfd433d..3730552c13 100644 --- a/src/regexp/regexp.go +++ b/src/regexp/regexp.go @@ -191,7 +191,7 @@ func compile(expr string, mode syntax.Flags, longest bool) (*Regexp, error) { longest: longest, }, } - if regexp.onepass == notOnePass { + if regexp.onepass == nil { regexp.prefix, regexp.prefixComplete = prog.Prefix() regexp.maxBitStateLen = maxBitStateLen(prog) } else { @@ -218,7 +218,7 @@ func (re *Regexp) get() *machine { return z } re.mu.Unlock() - z := progMachine(re.prog, re.onepass) + z := progMachine(re.prog) z.re = re return z } -- 2.30.9