]> Cypherpunks.ru repositories - gostls13.git/commitdiff
regexp: split one-pass execution out of machine struct
authorRuss Cox <rsc@golang.org>
Tue, 2 Oct 2018 13:29:47 +0000 (09:29 -0400)
committerRuss Cox <rsc@golang.org>
Fri, 12 Oct 2018 17:48:35 +0000 (17:48 +0000)
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
src/regexp/exec.go
src/regexp/exec_test.go
src/regexp/onepass.go
src/regexp/onepass_test.go
src/regexp/regexp.go

index 0fabeae59fcbe7f61cc0bbf9dc81971559f1761d..8cbc2962cbbcd54241be0ff6e131035c2616d1e8 100644 (file)
@@ -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")
        }
 }
 
index 271174670e624b4d6b7e992d3ae0a39b72bcd1e0..23908d22d5bacac1cfbff86ff2b24b6e3067db84 100644 (file)
@@ -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
index 02258e6e7421e274a17acf9ac1df54194a5eabff..148921932899baec227938044bdaebdb4ba3b5e6 100644 (file)
@@ -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 {
index 125be59a7d84b6e3a0232622df3ffa9552a95f95..2f3ce6f9f6cb877f9e4eda1d7c3d427ca9b1f741 100644 (file)
@@ -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
index 6b622ac3564d150afff652fa656f03b47ba85357..a0f2e390489389622b5ae5f54cf76cfaa029c279 100644 (file)
@@ -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) {
index dafcfd433dee0af9d4bfe4564134a895e16e2443..3730552c1366f52c53ca1d3c35c76aa187638c8c 100644 (file)
@@ -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
 }