Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wTK0m-000SBn-2y for pgsql-hackers@arkaria.postgresql.org; Sat, 30 May 2026 13:46:01 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wTK0l-0062Un-0k for pgsql-hackers@arkaria.postgresql.org; Sat, 30 May 2026 13:45:59 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wTK0k-0062Uf-1v for pgsql-hackers@lists.postgresql.org; Sat, 30 May 2026 13:45:59 +0000 Received: from mail-ej1-x62a.google.com ([2a00:1450:4864:20::62a]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wTK0g-00000000HFv-2cRk for pgsql-hackers@postgresql.org; Sat, 30 May 2026 13:45:57 +0000 Received: by mail-ej1-x62a.google.com with SMTP id a640c23a62f3a-bd8f6ef4ba6so2238077566b.3 for ; Sat, 30 May 2026 06:45:54 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1780148752; cv=none; d=google.com; s=arc-20240605; b=PC7PXHLcRAat3gSyxvh1mcE0rciUbL3+Astj++xdep+vCJp8eJcc21w5FHEwgtDu3o lsQeuTG8vm4PauoIg/Y57Osuer/ZZn47B8n0CWHO5BA22u0qt7a9ENbuvRv1S74tkyCw b3uXQQ7Sa/dqMi54UFNxd5aUYYoT5FcGIOjWfmfJP66BtwoyxOeoAmSB6mmlNTC9HrsI BfbH5C37wM+OPlejQK1blY0O6zBDGclpfluIhjUGxPEbppk4kXBjXuYm8dw3SfLhdyUq sWMcWXCWvUNzBYs8EwsdwlcprDLegFZgqFxmJpR/nXZtJGUWGDNyk4FATX6Nbxm9kJJg 9nAQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=cc:to:subject:message-id:date:from:reply-to:in-reply-to:references :mime-version:dkim-signature; bh=K4RAwiOYA31v3qJw/ajFctiQgfJtvdiw4M1tSMZixhQ=; fh=4u6Z7qM/JvUlfACMBU3PTNwse62+KrvLnd7kzktoi7Y=; b=X0iJSQ42/Lc73Ga9eqogylNCE4gOBrVQKyTRmZcTl6aBjuDOYBKagH7wamEngenpMZ i5Qj/g+IBnVzSSXd6Nk9pkz5VZ01g5u9jDBgg6ENy6NPsiAg8CJZvjwEG76flB/Rd5zf vc2huraCsy4Id2BjV7FNPbko7V15+AkR+uMZ0n/AOCffyPdXCCWoBzsCOhFQFqWiwqYH BYugwnb+A7w03xZBHqQyt0z1r3oyFUPcrHwOCD+nTb4CNzmBiTUxMmbV+PiN9biMtSsq M48lRfQV4wDFLhIalEwqv9VaWIbxcXHokhcAoQxzxgWoi1GTC784Yarh0188s8/vnUCo 4CQQ==; darn=postgresql.org ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1780148752; x=1780753552; darn=postgresql.org; h=cc:to:subject:message-id:date:from:reply-to:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=K4RAwiOYA31v3qJw/ajFctiQgfJtvdiw4M1tSMZixhQ=; b=nT3rfKDlBWbnDJB/Bhdw9N2zL1pK3/1v+Tid/CFgnrvRMyaXw6K+YDBBe5aImUvun9 hNvX3t7oo0wdyYWZwihLUd2oCmkMUK7Pcd9mEC3NKI7mPTeTKnIPG8o4/UpUPRSiEomz 6ojHu+f+q2ivwWiuu19qgi1FyQ5po7IXWGnNied/XWIJAZe/KoD6s2zSkS4uTu9nlwqQ wCg7EFxtYKQL7FHrJxKK9xWqh/eXnBXSYIiA+HdZzctXAb5FgEY8YF+t932Sg/n196bg K/AOG7CDUcjPT6lEA2VhPWOmGW8du0noBXeFKiR7Vsh9yht1EUPdurRFDcmyABw1xd2x +6Ew== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780148752; x=1780753552; h=cc:to:subject:message-id:date:from:reply-to:in-reply-to:references :mime-version:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=K4RAwiOYA31v3qJw/ajFctiQgfJtvdiw4M1tSMZixhQ=; b=W3UK2b+DBh0hNsS5PR9KgRqJUEjgWrSTODvjlYpG9xBbAgAKDjp7aW2pyINmrFr2nB 7ny6Ni7lM595c3KGSCG5TY/URNd26Rrp7p45yATiBVBGGBOIOqSTP95nEciimYzrNO42 GYvWvuw84yykCVE8emnFJ8edmfrm/ezHClPTfYhhsa9jBs0E55M7WzWfmAuAsM4swwQx dkyY4+/k7HbWTxZKfQkkl64ZiyFG4VWBA3J8WkplT9g35u+58J233iGBuLaGpE+PW8sW pDrIngUgQ9oIUnNcmQKbewvre2FpkjIohn0/xUoS8bK3+kJmykKMfNze1G+ODdx7Vv64 J4wA== X-Forwarded-Encrypted: i=1; AFNElJ/nbG3IZIYcTNaKtBVOFUrri1bvhfbmaB7iabhjLXHK7WWoKrNvnEu9MbusFOkv0P8eHCqke1cqT3WUz3KN@postgresql.org X-Gm-Message-State: AOJu0Yx7SsU37JxMNprDXjbWdVXCMpFEF4wbPV57XDNBG+GqR6X//A4e uyflD9KFv6pLTODxBUD5zqAmj0Tqsaj2AIKFpC01rvaSUPim8NB0wDznEU/Hy+okfEng8ygM6KG zau+AuPEx/9A1xc88WYUWJgdl7NhjxmI= X-Gm-Gg: Acq92OE84e0t4AtyjcUqi+ewn92DQhvwqBzTrQdMQYGQa/ZV18wS1Lax2a7XNOknIlh mtx+XrU7YcZNsFqHo0WqPDDeU/wkWFvOM/hCJHKJRnkLjXd2atHDIcGJu0LkimE/mgiqZbMrec/ 6Q9Oa40g16sRXReBYFf10cbwlMq7OXVxXLTgQrLJt+4GFa0IYKQnjuYCX1YLm5E7xWxChXX+Zkz fA0ZhaTqp0NHCVVIgMGJT6zKjMr1kQ+nvK2uobzo7TwAfWnGWE4QNs4UN4dYKFgiJCHMArZmRr9 iwTC+XWrwGHJ6sFPj+jrmps8rcjUfmPbI6mYw+N+lJg67b37F/EXbShYqCYiIw== X-Received: by 2002:a17:907:270d:b0:bdf:b95e:98df with SMTP id a640c23a62f3a-beab394bc56mr131715566b.7.1780148751320; Sat, 30 May 2026 06:45:51 -0700 (PDT) MIME-Version: 1.0 References: <20260502.140304.670813149418899420.ishii@postgresql.org> <20260505.090124.365339750969814137.ishii@postgresql.org> <20260517.190023.159085681032648582.ishii@postgresql.org> In-Reply-To: Reply-To: assam258@gmail.com From: Henson Choi Date: Sat, 30 May 2026 22:45:39 +0900 X-Gm-Features: AVHnY4LN28R9MX39BEZq2v8ovKU0wfClxEsWT-Fv1pyi4JaZ_lNaiw3KGj0-x8A Message-ID: Subject: Re: Row pattern recognition To: jian he Cc: Tatsuo Ishii , zsolt.parragi@percona.com, sjjang112233@gmail.com, vik@postgresfriends.org, er@xs4all.nl, jacob.champion@enterprisedb.com, david.g.johnston@gmail.com, peter@eisentraut.org, li.evan.chao@gmail.com, pgsql-hackers@postgresql.org Content-Type: multipart/alternative; boundary="000000000000f84b010653092df1" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --000000000000f84b010653092df1 Content-Type: text/plain; charset="UTF-8" [mailing-list reply draft -- pgsql-hackers, RPR thread. In-reply-to Jian's round-3 review, 2026-05-30 06:45 (Message-ID CACJufxEsaU8GQ4yeXTWhAO8VjbrZTh5CpvUqz= 4a3T0Cwz44pA@mail.gmail.com). Covers R3-1..R3-4 (comments) and the three attached refactors R3-5a/b/c. All accepted; fold into the v48 refactor series.] Subject: Re: Row pattern recognition Hi Jian, Thanks for the round-3 pass -- all seven are good catches. Going through them inline below, with a summary of decisions at the end. > Comments like "Tests line 251" should not appear in SQL, since the > line number would change. > So, we need to rephrase this comment. Please don't delete it, it may > clarify the patch for future readers. Agreed on both counts -- the intent (this case exercises the child->max == RPR_QUANTITY_INF branch in mergeConsecutiveVars, where a finite prev meets an infinite child) is worth keeping; only the "line 251" anchor is fragile. I'll rephrase it to name the branch and the condition it covers instead of the line, so it survives any later shuffle. There's a parallel one a few tests down -- "Tests line 325 ... in mergeConsecutiveGroups" -- with the same problem, so I'll fix both together on the same principle. > The "at the END" confuses me. It may also refers to RPR_VARID_END / > RPRElemIsEnd. > How about something like: > "Add the state to the end of the ctx->states linked list, but only if > a duplicate state is not already present." Your wording is clearer than mine -- I'll take it almost verbatim. "at the END" there meant the list tail, and capitalizing it was exactly the wrong choice next to the END element kind. > I think a bunch of these uppercase ENDs should just be lowercase "end", > [...] > Could we lowercase the list-end ones and keep uppercase only when actually > referring to the END element kind? Yes -- that's the right rule, and I'll go through them on it: lowercase "end" for the linked-list tail, uppercase END only when it means the element kind (RPRElemIsEnd / RPR_VARID_END). On the two you singled out: - nfa_match, "Non-VAR elements (ALT, END, FIN) ..." -- element kind, listed with the others, stays uppercase. Agreed. - "advance through END chain inline ..." -- this one is actually the element kind too: it walks the chain of group-END *elements* up to the absorption judgment point, not the state list. So it stays uppercase, but since it tripped you up I'll reword it to "chain of END elements" to make that unambiguous rather than leaning on the reader to infer it. > The comment under nfa_advance_var is confusing (for me). > /* After a successful match, count >= 1, so at least one must be true */ > nfa_advance_var doesn't actually know anything about match status (i > think), it can be reached with count == 0. > [...] and it does fire, so the comment's premise isn't quite right. You're right, and thanks for confirming it with the elog. nfa_advance_var is part of the advance phase, which generates every reachable next state regardless of what matched -- so it's routinely entered with count == 0, for a VAR just reached through an ALT branch or a group that hasn't been evaluated yet. The comment carries match-phase reasoning into a phase that has no notion of matching. The Assert above still holds, but on the structure of a VAR's bounds, not on any prior match, so I'll reword the comment to say that instead. > please check the attached minor refactoring. > 1. Refactor struct WindowAggState: Remove the nfaStateSize and > nfaVisitedNWords fields because they are constant, and we can easily > compute it, [...] not necessary to stay in WindowAggState. > 2. Rename nfa_context_alloc() to nfa_context_make(), nfa_state_alloc() > to nfa_state_make(), nfa_state_create() to nfa_state_clone(). > Rationale: We have makeNode [...]. In tablecmd.c, we have > CloneForeignKeyConstraints, which is based on existing information, > creating a new node, here we are doing the same in nfa_state_create. > 3. Refactor functions: nfa_advance_alt, nfa_advance_begin, > nfa_advance_end, and nfa_advance_var. Because the > (RPRPatternElement *elem) parameter is unnecessary. Looked at all three. 0002 and 0003 I'll take; 0001 I'd split. 1. The two fields differ. nfaVisitedNWords -- yes: in the current tree it's read only once, at init, to size the visited bitmap, since the per-row reset clears just the high-water [min,max] range. Demoting it to a local is clean. (Heads up: the patch is on v47, before that high-water-mark change, so its nfa_advance hunk no longer applies and drops out on rebase.) nfaStateSize I'd keep. The patch recomputes it inside the allocator, i.e. on every state allocation -- the hottest path in the engine; a pathological (V1|...|Vk)* can allocate hundreds of millions of states. It's a loop-invariant fixed at init, so I'd rather keep it precomputed than rebuild offsetof + maxDepth*4 each alloc -- the same reasoning I gave for keeping the frame offsets out of the per-row loop. 2. make/clone: agreed, and there's a closer precedent than I'd have reached for -- our own regex NFA engine already clones states (clonesuccessorstates / cloneouts in regcomp.c), so clone is the right word for duplicating an NFA state, not just the CloneForeignKey sense. The pair reads well too: make for the blank allocation, clone for building a state from an existing one's counts. (create was itself an earlier rename of mine from clone "for clarity," so this just lands us back on clone, which I now agree is better.) When I apply it I'll also tidy two comments the rename left behind -- the nfa_state_make header now reads "a new RPRNFAState state," and nfa_state_clone's body comment still says "Create." 3. elem parameter: here I'd keep it, on the same grounds as nfaStateSize. The four functions have a single caller, nfa_advance_state, which already computes elem = &elements[state->elemIdx] for its own mark-visited check and switch, and hands it down. Dropping the parameter makes each function rebuild that same address on the per-dispatch advance path -- discarding a value the caller already holds. And with one trusted caller, the mismatch the change guards against can't really arise. The cost either way is tiny; I'd just rather keep the value flowing from where it's already computed than recompute it, which is the same call I made on nfaStateSize. Summary of decisions, in the order above: "Tests line N" test comments Rephrase -- name the branch, not the line (both) "at the END" wording Accept -- your wording, near-verbatim uppercase END cleanup Accept -- lowercase the list-end ones, keep the kind nfa_advance_var count comment Reword -- wrong premise; state the structural reason 0001 nfaVisitedNWords Accept -- demote to a local 0001 nfaStateSize Keep -- hottest path; leave it precomputed 0002 make / clone rename Accept -- plus tidy two stray comments 0003 drop the elem parameter Keep -- single caller already holds it So the next revision carries the four comment fixes, 0002, and the nfaVisitedNWords half of 0001; nfaStateSize and the elem parameter I'd keep as they are. Happy to be talked round on either if you see a cost I'm missing. Thanks again, Henson --000000000000f84b010653092df1 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
[mailing-list reply draft -- pgsql-hackers, RPR thread.=C2=A0In-reply-to Jian's round-3 review, 2026-05-30 06:45
=C2=A0(Me= ssage-ID CACJufxEsaU8GQ4yeXTWhAO8VjbrZTh5CpvUqz=3D4a3T0Cwz44pA@mail.gmail.com).
=C2=A0Covers R3-= 1..R3-4 (comments) and the three attached refactors R3-5a/b/c.
=C2=A0All= accepted; fold into the v48 refactor series.]

Subject: Re: Row patt= ern recognition

Hi Jian,

Thanks for the round-3 pass -- all s= even are good catches. Going through
them inline below, with a summary o= f decisions at the end.

> Comments like "Tests line 251"= ; should not appear in SQL, since the
> line number would change.
= > So, we need to rephrase this comment. Please don't delete it, it m= ay
> clarify the patch for future readers.

Agreed on both coun= ts -- the intent (this case exercises the
child->max =3D=3D RPR_QUANT= ITY_INF branch in mergeConsecutiveVars, where a
finite prev meets an inf= inite child) is worth keeping; only the "line 251"
anchor is f= ragile. I'll rephrase it to name the branch and the condition
it cov= ers instead of the line, so it survives any later shuffle.

There'= ;s a parallel one a few tests down -- "Tests line 325 ... in
mergeC= onsecutiveGroups" -- with the same problem, so I'll fix both
to= gether on the same principle.

> The "at the END" confus= es me. It may also refers to RPR_VARID_END /
> RPRElemIsEnd.
> = How about something like:
> "Add the state to the end of the ctx= ->states linked list, but only if
> a duplicate state is not alrea= dy present."

Your wording is clearer than mine -- I'll take= it almost verbatim. "at the
END" there meant the list tail, a= nd capitalizing it was exactly the wrong
choice next to the END element = kind.

> I think a bunch of these uppercase ENDs should just be lo= wercase "end",
> [...]
> Could we lowercase the list-= end ones and keep uppercase only when actually
> referring to the END= element kind?

Yes -- that's the right rule, and I'll go thr= ough them on it: lowercase
"end" for the linked-list tail, upp= ercase END only when it means the
element kind (RPRElemIsEnd / RPR_VARID= _END). On the two you singled out:

=C2=A0 - nfa_match, "Non-VAR= elements (ALT, END, FIN) ..." -- element kind,
=C2=A0 =C2=A0 liste= d with the others, stays uppercase. Agreed.

=C2=A0 - "advance t= hrough END chain inline ..." -- this one is actually the
=C2=A0 =C2= =A0 element kind too: it walks the chain of group-END *elements* up to the<= br>=C2=A0 =C2=A0 absorption judgment point, not the state list. So it stays= uppercase,
=C2=A0 =C2=A0 but since it tripped you up I'll reword it= to "chain of END elements"
=C2=A0 =C2=A0 to make that unambig= uous rather than leaning on the reader to infer it.

> The comment= under nfa_advance_var is confusing (for me).
> /* After a successful= match, count >=3D 1, so at least one must be true */
> nfa_advanc= e_var doesn't actually know anything about match status (i
> thin= k), it can be reached with count =3D=3D 0.
> [...] and it does fire, = so the comment's premise isn't quite right.

You're right= , and thanks for confirming it with the elog. nfa_advance_var
is part of= the advance phase, which generates every reachable next state
regardles= s of what matched -- so it's routinely entered with count =3D=3D 0,
= for a VAR just reached through an ALT branch or a group that hasn't bee= n
evaluated yet. The comment carries match-phase reasoning into a phase = that
has no notion of matching. The Assert above still holds, but on the=
structure of a VAR's bounds, not on any prior match, so I'll re= word the
comment to say that instead.

> please check the attac= hed minor refactoring.
> 1.=C2=A0 Refactor struct WindowAggState: Rem= ove the nfaStateSize and
> =C2=A0 =C2=A0 nfaVisitedNWords fields beca= use they are constant, and we can easily
> =C2=A0 =C2=A0 compute it, = [...] not necessary to stay in WindowAggState.
> 2. Rename nfa_contex= t_alloc() to nfa_context_make(), nfa_state_alloc()
> =C2=A0 =C2=A0to = nfa_state_make(), nfa_state_create() to nfa_state_clone().
> =C2=A0 = =C2=A0Rationale: We have makeNode [...]. In tablecmd.c, we have
> =C2= =A0 =C2=A0CloneForeignKeyConstraints, which is based on existing informatio= n,
> =C2=A0 =C2=A0creating a new node, here we are doing the same in = nfa_state_create.
> 3. Refactor functions: nfa_advance_alt, nfa_advan= ce_begin,
> =C2=A0 =C2=A0nfa_advance_end, and nfa_advance_var. Becaus= e the
> =C2=A0 =C2=A0(RPRPatternElement *elem) parameter is unnecessa= ry.

Looked at all three. 0002 and 0003 I'll take; 0001 I'd s= plit.

=C2=A0 1. The two fields differ. nfaVisitedNWords -- yes: in t= he current tree
=C2=A0 =C2=A0 =C2=A0it's read only once, at init, to= size the visited bitmap, since the
=C2=A0 =C2=A0 =C2=A0per-row reset cl= ears just the high-water [min,max] range. Demoting it
=C2=A0 =C2=A0 =C2= =A0to a local is clean. (Heads up: the patch is on v47, before that
=C2= =A0 =C2=A0 =C2=A0high-water-mark change, so its nfa_advance hunk no longer = applies and
=C2=A0 =C2=A0 =C2=A0drops out on rebase.)

=C2=A0 =C2= =A0 =C2=A0nfaStateSize I'd keep. The patch recomputes it inside the all= ocator,
=C2=A0 =C2=A0 =C2=A0i.e. on every state allocation -- the hottes= t path in the engine; a
=C2=A0 =C2=A0 =C2=A0pathological (V1|...|Vk)* ca= n allocate hundreds of millions of states.
=C2=A0 =C2=A0 =C2=A0It's = a loop-invariant fixed at init, so I'd rather keep it precomputed
= =C2=A0 =C2=A0 =C2=A0than rebuild offsetof + maxDepth*4 each alloc -- the sa= me reasoning I
=C2=A0 =C2=A0 =C2=A0gave for keeping the frame offsets ou= t of the per-row loop.

=C2=A0 2. make/clone: agreed, and there's= a closer precedent than I'd have
=C2=A0 =C2=A0 =C2=A0reached for --= our own regex NFA engine already clones states
=C2=A0 =C2=A0 =C2=A0(clo= nesuccessorstates / cloneouts in regcomp.c), so clone is the right
=C2= =A0 =C2=A0 =C2=A0word for duplicating an NFA state, not just the CloneForei= gnKey sense.
=C2=A0 =C2=A0 =C2=A0The pair reads well too: make for the b= lank allocation, clone for
=C2=A0 =C2=A0 =C2=A0building a state from an = existing one's counts. (create was itself an
=C2=A0 =C2=A0 =C2=A0ear= lier rename of mine from clone "for clarity," so this just lands = us
=C2=A0 =C2=A0 =C2=A0back on clone, which I now agree is better.) When= I apply it I'll also
=C2=A0 =C2=A0 =C2=A0tidy two comments the rena= me left behind -- the nfa_state_make header
=C2=A0 =C2=A0 =C2=A0now read= s "a new RPRNFAState state," and nfa_state_clone's body comme= nt
=C2=A0 =C2=A0 =C2=A0still says "Create."

=C2=A0 3. e= lem parameter: here I'd keep it, on the same grounds as nfaStateSize.=C2=A0 =C2=A0 =C2=A0The four functions have a single caller, nfa_advance_= state, which
=C2=A0 =C2=A0 =C2=A0already computes elem =3D &elements= [state->elemIdx] for its own
=C2=A0 =C2=A0 =C2=A0mark-visited check a= nd switch, and hands it down. Dropping the
=C2=A0 =C2=A0 =C2=A0parameter= makes each function rebuild that same address on the
=C2=A0 =C2=A0 =C2= =A0per-dispatch advance path -- discarding a value the caller already
= =C2=A0 =C2=A0 =C2=A0holds. And with one trusted caller, the mismatch the ch= ange guards
=C2=A0 =C2=A0 =C2=A0against can't really arise. The cost= either way is tiny; I'd just
=C2=A0 =C2=A0 =C2=A0rather keep the va= lue flowing from where it's already computed than
=C2=A0 =C2=A0 =C2= =A0recompute it, which is the same call I made on nfaStateSize.

Summ= ary of decisions, in the order above:

=C2= =A0 "Tests line N" test comments =C2=A0 =C2=A0 Rephrase -- name t= he branch, not the line (both)
=C2=A0 "at the END" wording =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 Accept -- your wording, near-verbati= m
=C2=A0 uppercase END cleanup =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= Accept -- lowercase the list-end ones, keep the kind
=C2=A0 nfa_advance_= var count comment =C2=A0 =C2=A0Reword -- wrong premise; state the structura= l reason
=C2=A0 0001 nfaVisitedNWords =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0Accept -- demote to a local
=C2=A0 0001 nfaStateSize =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0Keep -- hottest path; leave it= precomputed
=C2=A0 0002 make / clone rename =C2=A0 =C2=A0 =C2=A0 =C2=A0= Accept -- plus tidy two stray comments
=C2=A0 0003 drop the elem parame= ter =C2=A0 =C2=A0 Keep -- single caller already holds it


So t= he next revision carries the four comment fixes, 0002, and the
nfaVisite= dNWords half of 0001; nfaStateSize and the elem parameter I'd keep
a= s they are. Happy to be talked round on either if you see a cost I'mmissing.

Thanks again,
Henson
--000000000000f84b010653092df1--