Skip to content

Commit cef200f

Browse files
authored
perf: speed up termination of ElimDeadBranches compiler pass (#11362)
This PR accelerates termination of the ElimDeadBranches compiler pass. The implementation addresses situations such as `choice [none, some top]` which can be summarized to `top` because `Option` has only two constructors and all constructor arguments are `top`.
1 parent 8ace95f commit cef200f

File tree

1 file changed

+78
-34
lines changed

1 file changed

+78
-34
lines changed

src/Lean/Compiler/LCNF/ElimDeadBranches.lean

Lines changed: 78 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -35,25 +35,44 @@ inductive Value where
3535
A set of values are possible.
3636
-/
3737
| choice (vs : List Value)
38-
deriving Inhabited, Repr
38+
deriving Inhabited
3939

4040
namespace Value
4141

4242
-- TODO: parameterize
4343
def maxValueDepth := 8
4444

4545
protected partial def beq : Value → Value → Bool
46-
| bot, bot => true
47-
| top, top => true
48-
| ctor i1 vs1 , ctor i2 vs2 =>
49-
i1 == i2 && Array.isEqv vs1 vs2 Value.beq
50-
| choice vs1 , choice vs2 =>
51-
let isSubset as bs := as.all (fun a => bs.any fun b => Value.beq a b)
52-
isSubset vs1 vs2 && isSubset vs2 vs1
53-
| _, _ => false
46+
| bot, bot => true
47+
| top, top => true
48+
| ctor i1 vs1 , ctor i2 vs2 =>
49+
i1 == i2 && Array.isEqv vs1 vs2 Value.beq
50+
| choice vs1 , choice vs2 =>
51+
let isSubset as bs := as.all (fun a => bs.any fun b => Value.beq a b)
52+
isSubset vs1 vs2 && isSubset vs2 vs1
53+
| _, _ => false
5454

5555
instance : BEq Value := ⟨Value.beq⟩
5656

57+
protected partial def toFormat : Value → Format
58+
| bot => "⊥"
59+
| top => "⊤"
60+
| ctor i vs =>
61+
if vs.isEmpty then
62+
format i
63+
else
64+
.paren <| format i ++ .join (vs.toList.map fun v => " " ++ Value.toFormat v)
65+
| choice vs =>
66+
.paren <| .joinSep (vs.map Value.toFormat) " | "
67+
68+
instance : Repr Value where
69+
reprPrec v _ := Value.toFormat v
70+
71+
def inductValOfCtor (ctorName : Name) (env : Environment) : InductiveVal := Id.run do
72+
let some (.ctorInfo info) ← env.find? ctorName | unreachable!
73+
let some (.inductInfo info) ← env.find? info.induct | unreachable!
74+
return info
75+
5776
mutual
5877

5978
/--
@@ -62,32 +81,60 @@ is a constructor that is already contained within `vs` try to detect
6281
the difference between these values and merge them accordingly into a
6382
choice node further down the tree.
6483
-/
65-
partial def addChoice (vs : List Value) (v : Value) : List Value :=
84+
partial def addChoice (env : Environment) (vs : List Value) (v : Value) : List Value :=
6685
match vs, v with
6786
| [], v => [v]
6887
| v1@(ctor i1 _ ) :: cs, ctor i2 _ =>
6988
if i1 == i2 then
70-
(merge v1 v) :: cs
89+
(merge env v1 v) :: cs
7190
else
72-
v1 :: addChoice cs v
91+
v1 :: addChoice env cs v
7392
| _, _ => panic! "invalid addChoice"
7493

7594
/--
7695
Merge two values into one. `bot` is the neutral element, `top` the annihilator.
7796
-/
78-
partial def merge (v1 v2 : Value) : Value :=
79-
match v1, v2 with
80-
| bot, v | v, bot => v
81-
| top, _ | _, top => top
82-
| ctor i1 vs1, ctor i2 vs2 =>
83-
if i1 == i2 then
84-
ctor i1 (Array.zipWith merge vs1 vs2)
97+
partial def merge (env : Environment) (v1 v2 : Value) : Value :=
98+
let newValue :=
99+
match v1, v2 with
100+
| bot, v | v, bot => v
101+
| top, _ | _, top => top
102+
| ctor i1 vs1, ctor i2 vs2 =>
103+
if i1 == i2 then
104+
ctor i1 (Array.zipWith (merge env) vs1 vs2)
105+
else
106+
choice [v1, v2]
107+
| choice vs1, choice vs2 =>
108+
choice (vs1.foldl (addChoice env) vs2)
109+
| choice vs, v | v, choice vs =>
110+
choice (addChoice env vs v)
111+
match newValue with
112+
| .top | .bot => newValue
113+
| .choice vs => cleanup vs
114+
| .ctor ctorName .. =>
115+
if eligible newValue && inductHasNumCtors ctorName env 1 then
116+
top
85117
else
86-
choice [v1, v2]
87-
| choice vs1, choice vs2 =>
88-
choice (vs1.foldl addChoice vs2)
89-
| choice vs, v | v, choice vs =>
90-
choice (addChoice vs v)
118+
newValue
119+
where
120+
cleanup (vs : List Value) : Value := Id.run do
121+
if vs.all eligible then
122+
let .ctor ctorName .. := vs.head! | unreachable!
123+
if inductHasNumCtors ctorName env vs.length then
124+
top
125+
else
126+
choice vs
127+
else
128+
choice vs
129+
130+
inductHasNumCtors (ctorName : Name) (env : Environment) (n : Nat) : Bool := Id.run do
131+
let induct := inductValOfCtor ctorName env
132+
n == induct.numCtors
133+
134+
@[inline]
135+
eligible (value : Value) : Bool := Id.run do
136+
let .ctor _ args := value | return false
137+
args.all (· == .top)
91138

92139
end
93140

@@ -106,19 +153,16 @@ where
106153
| remainingDepth + 1 =>
107154
match v with
108155
| ctor i vs =>
109-
let typeName := i.getPrefix
110-
if forbiddenTypes.contains typeName then
156+
let induct := inductValOfCtor i env
157+
if forbiddenTypes.contains induct.name then
111158
top
112159
else
113160
let cont forbiddenTypes' :=
114161
ctor i (vs.map (go · forbiddenTypes' remainingDepth))
115-
match env.find? typeName with
116-
| some (.inductInfo type) =>
117-
if type.isRec then
118-
cont <| forbiddenTypes.insert typeName
119-
else
120-
cont forbiddenTypes
121-
| _ => cont forbiddenTypes
162+
if induct.isRec then
163+
cont <| forbiddenTypes.insert induct.name
164+
else
165+
cont forbiddenTypes
122166
| choice vs =>
123167
let vs := vs.map (go · forbiddenTypes remainingDepth)
124168
if vs.elem top then
@@ -129,7 +173,7 @@ where
129173

130174
/-- Widening operator that guarantees termination in our abstract interpreter. -/
131175
def widening (env : Environment) (v1 v2 : Value) : Value :=
132-
truncate env (merge v1 v2)
176+
truncate env (merge env v1 v2)
133177

134178
/--
135179
Check whether a certain constructor is part of a `Value` by name.

0 commit comments

Comments
 (0)