This PR fixes a performance regression introduced by enabling `backward.whnf.reducibleClassField` (https://github.com/leanprover/lean4/pull/12538). The `isNonTrivialRegular` function in `ExprDefEq` was classifying class projections as nontrivial at all transparency levels, but the extra `.instances` reduction in `unfoldDefault` that motivates this classification only applies at `.reducible` transparency. At higher transparency levels, the nontrivial classification caused unnecessary heuristic comparison attempts in `isDefEqDelta` that cascaded through BitVec reductions, causing elaboration of `Lean.Data.Json.Parser` to double from ~3.6G to ~7.2G instructions. The fix restricts the nontrivial classification to `.reducible` transparency only, matching the scope of `unfoldDefault`'s extra reduction behavior. 🤖 Prepared with Claude Code Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
71 lines
2.3 KiB
Text
71 lines
2.3 KiB
Text
import Std.Internal.Parsec.String
|
|
|
|
/-!
|
|
Regression test for performance of `backward.whnf.reducibleClassField` with UInt16 bitwise operations.
|
|
|
|
The `isNonTrivialRegular` function in ExprDefEq should only classify class projections as
|
|
nontrivial at `.reducible` transparency (where `unfoldDefault`'s extra reduction applies).
|
|
At higher transparency levels, the classification causes unnecessary heuristic attempts
|
|
that cascade through BitVec reductions.
|
|
|
|
Without the fix, the `uEscape` function below took ~4.5G instructions.
|
|
With the fix, it takes ~0.94G instructions.
|
|
-/
|
|
|
|
set_option backward.whnf.reducibleClassField true
|
|
set_option maxHeartbeats 400000
|
|
|
|
open Std.Internal.Parsec
|
|
open Std.Internal.Parsec.String
|
|
|
|
def hexChar : Parser UInt16 := do
|
|
let c ← any
|
|
if '0' <= c && c <= '9' then
|
|
pure $ (c.val - '0'.val).toUInt16
|
|
else if 'a' <= c && c <= 'f' then
|
|
pure $ (c.val - 'a'.val + 10).toUInt16
|
|
else if 'A' <= c && c <= 'F' then
|
|
pure $ (c.val - 'A'.val + 10).toUInt16
|
|
else
|
|
fail "invalid hex character"
|
|
|
|
def finishSurrogatePair (low : UInt16) : Parser Char := do
|
|
let c ← any
|
|
if c != '\\' then fail ""
|
|
let c ← any
|
|
if c != 'u' then fail ""
|
|
let c ← any
|
|
if c != 'd' && c != 'D' then fail ""
|
|
let u2 ← hexChar; let u3 ← hexChar; let u4 ← hexChar
|
|
let val := (u2 <<< 8) ||| (u3 <<< 4) ||| u4
|
|
if val < 0xC00 then fail ""
|
|
let charVal := (((low.toUInt32 &&& 0x3FF) <<< 10) ||| (val.toUInt32 &&& 0x3FF)) + 0x10000
|
|
if h : charVal.isValidChar then
|
|
return ⟨charVal, h⟩
|
|
else
|
|
fail ""
|
|
|
|
def escapedChar : Parser Char := do
|
|
let c ← any
|
|
match c with
|
|
| '\\' => return '\\'
|
|
| '"' => return '"'
|
|
| '/' => return '/'
|
|
| 'b' => return '\x08'
|
|
| 'f' => return '\x0c'
|
|
| 'n' => return '\n'
|
|
| 'r' => return '\x0d'
|
|
| 't' => return '\t'
|
|
| 'u' =>
|
|
let u1 ← hexChar; let u2 ← hexChar; let u3 ← hexChar; let u4 ← hexChar
|
|
let val := (u1 <<< 12) ||| (u2 <<< 8) ||| (u3 <<< 4) ||| u4
|
|
if h : val < 0xD800 then
|
|
return ⟨val.toUInt32, Or.inl h⟩
|
|
else if h' : val < 0xE000 then
|
|
if val < 0xDC00 then
|
|
attempt (finishSurrogatePair val) <|> pure '\ufffd'
|
|
else
|
|
return '\ufffd'
|
|
else
|
|
return ⟨val.toUInt32, Or.inr ⟨Nat.not_lt.mp h', Nat.lt_trans val.toFin.isLt (by decide)⟩⟩
|
|
| _ => fail "illegal \\u escape"
|