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