fsub()/fgsub() with perl = FALSE replaced a different match than base R
Where alternatives can match at the same position, base R’s default (TRE) engine takes the longest match and PCRE2 takes the first alternative that matches: sub("a|ab", "X", "abc") gives "Xc", fsub() gives "Xbc". Whether something matches (fgrepl()) is unaffected. List the longer alternative first ("ab|a") to get the same result from both. Everything else about perl = FALSE follows base R’s rules – see Chapter 3.
Patterns that only PCRE understands (lookarounds, atomic groups, possessive quantifiers, named groups) run with perl = TRUE semantics even when perl = FALSE, still on the fast path. fcount() delegates only an empty pattern to base::gregexpr(), because base R counts those matches in characters.