Skip to content

Commit 5dde403

Browse files
authored
fix: toposort declarations to ensure proper constant initialization (#11388)
This PR is a followup of #11381 and enforces the invariants on ordering of closed terms and constants required by the EmitC pass properly by toposorting before saving the declarations into the Environment.
1 parent 8639afa commit 5dde403

File tree

5 files changed

+99
-37
lines changed

5 files changed

+99
-37
lines changed

src/Lean/Compiler/IR.lean

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public import Lean.Compiler.IR.Sorry
2727
public import Lean.Compiler.IR.ToIR
2828
public import Lean.Compiler.IR.ToIRType
2929
public import Lean.Compiler.IR.Meta
30+
public import Lean.Compiler.IR.Toposort
3031

3132
-- The following imports are not required by the compiler. They are here to ensure that there
3233
-- are no orphaned modules.
@@ -71,6 +72,7 @@ def compile (decls : Array Decl) : CompilerM (Array Decl) := do
7172
decls ← updateSorryDep decls
7273
logDecls `result decls
7374
checkDecls decls
75+
decls ← toposortDecls decls
7476
addDecls decls
7577
inferMeta decls
7678
return decls

src/Lean/Compiler/IR/Toposort.lean

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/-
2+
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Henrik Böving
5+
-/
6+
module
7+
8+
prelude
9+
public import Lean.Compiler.IR.CompilerM
10+
11+
/-!
12+
This module "topologically sorts" an SCC of decls (an SCC of decls in the pipeline may in fact
13+
contain more than one SCC at the moment). This is relevant because EmitC relies on the invariant
14+
that the constants (and in particular also the closed terms) occur in a reverse topologically sorted
15+
order for emitting them.
16+
-/
17+
18+
namespace Lean.IR
19+
20+
structure TopoState where
21+
declsMap : Std.HashMap Name Decl
22+
seen : Std.HashSet Name
23+
order : Array Decl
24+
25+
abbrev ToposortM := StateRefT TopoState CompilerM
26+
27+
partial def toposort (decls : Array Decl) : CompilerM (Array Decl) := do
28+
let declsMap := .ofList (decls.toList.map (fun d => (d.name, d)))
29+
let (_, s) ← go decls |>.run {
30+
declsMap,
31+
seen := .emptyWithCapacity decls.size,
32+
order := .emptyWithCapacity decls.size
33+
}
34+
return s.order
35+
where
36+
go (decls : Array Decl) : ToposortM Unit := do
37+
decls.forM process
38+
39+
process (decl : Decl) : ToposortM Unit := do
40+
if (← get).seen.contains decl.name then
41+
return ()
42+
43+
modify fun s => { s with seen := s.seen.insert decl.name }
44+
let .fdecl (body := body) .. := decl | unreachable!
45+
processBody body
46+
modify fun s => { s with order := s.order.push decl }
47+
48+
processBody (b : FnBody) : ToposortM Unit := do
49+
match b with
50+
| .vdecl _ _ e b =>
51+
match e with
52+
| .fap c .. | .pap c .. =>
53+
if let some decl := (← get).declsMap[c]? then
54+
process decl
55+
| _ => pure ()
56+
processBody b
57+
| .jdecl _ _ v b =>
58+
processBody v
59+
processBody b
60+
| .case _ _ _ cs => cs.forM (fun alt => processBody alt.body)
61+
| .jmp .. | .ret .. | .unreachable => return ()
62+
| _ => processBody b.body
63+
64+
65+
public def toposortDecls (decls : Array Decl) : CompilerM (Array Decl) := do
66+
let (externDecls, otherDecls) := decls.partition (fun decl => decl.isExtern)
67+
let otherDecls ← toposort otherDecls
68+
return externDecls ++ otherDecls
69+
70+
end Lean.IR

src/Lean/Compiler/LCNF/ExtractClosed.lean

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -141,27 +141,17 @@ def visitDecl (decl : Decl) : M Decl := do
141141

142142
end ExtractClosed
143143

144-
partial def Decl.extractClosed (decl : Decl) (sccDecls : Array Decl) : CompilerM (Decl × Array Decl) := do
144+
partial def Decl.extractClosed (decl : Decl) (sccDecls : Array Decl) : CompilerM (Array Decl) := do
145145
let ⟨decl, s⟩ ← ExtractClosed.visitDecl decl |>.run { baseName := decl.name, sccDecls } |>.run {}
146-
return (decl, s.decls)
146+
return s.decls.push decl
147147

148148
def extractClosed : Pass where
149149
phase := .mono
150150
name := `extractClosed
151151
run := fun decls => do
152152
if (← getConfig).extractClosed then
153-
let mut changedDecls := Array.emptyWithCapacity decls.size
154-
let mut closedDecls := #[]
155-
for decl in decls do
156-
let (change, new) ← decl.extractClosed decls
157-
changedDecls := changedDecls.push change
158-
closedDecls := closedDecls ++ new
159-
160-
/-
161-
EmitC later relies on the fact that within an SCC the closed term declarations come first,
162-
then the declarations that rely on them.
163-
-/
164-
return closedDecls ++ changedDecls
153+
decls.foldlM (init := #[]) fun newDecls decl =>
154+
return newDecls ++ (← decl.extractClosed decls)
165155
else
166156
return decls
167157

tests/lean/run/emptyLcnf.lean

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,17 @@ trace: [Compiler.result] size: 0
1111
def f x : Nat :=
1212
1313
---
14-
trace: [Compiler.result] size: 1
14+
trace: [Compiler.result] size: 5
15+
def _private.lean.run.emptyLcnf.0._eval._lam_0 _x.1 _x.2 _y.3 _y.4 _y.5 _y.6 _y.7 _y.8 _y.9 : EST.Out Lean.Exception
16+
lcAny PUnit :=
17+
let _x.10 := Lean.Compiler.compile _x.1 _y.7 _y.8 _y.9;
18+
cases _x.10 : EST.Out Lean.Exception lcAny PUnit
19+
| EST.Out.ok a.11 a.12 =>
20+
let _x.13 := @EST.Out.ok ◾ ◾ ◾ _x.2 a.12;
21+
return _x.13
22+
| EST.Out.error a.14 a.15 =>
23+
return _x.10
24+
[Compiler.result] size: 1
1525
def _private.lean.run.emptyLcnf.0._eval._closed_0 : String :=
1626
let _x.1 := "f";
1727
return _x.1
@@ -31,16 +41,6 @@ trace: [Compiler.result] size: 1
3141
let _x.2 := _eval._closed_2.2;
3242
let _x.3 := Array.push ◾ _x.2 _x.1;
3343
return _x.3
34-
[Compiler.result] size: 5
35-
def _private.lean.run.emptyLcnf.0._eval._lam_0 _x.1 _x.2 _y.3 _y.4 _y.5 _y.6 _y.7 _y.8 _y.9 : EST.Out Lean.Exception
36-
lcAny PUnit :=
37-
let _x.10 := Lean.Compiler.compile _x.1 _y.7 _y.8 _y.9;
38-
cases _x.10 : EST.Out Lean.Exception lcAny PUnit
39-
| EST.Out.ok a.11 a.12 =>
40-
let _x.13 := @EST.Out.ok ◾ ◾ ◾ _x.2 a.12;
41-
return _x.13
42-
| EST.Out.error a.14 a.15 =>
43-
return _x.10
4444
[Compiler.result] size: 8
4545
def _private.lean.run.emptyLcnf.0._eval a.1 a.2 a.3 : EST.Out Lean.Exception lcAny PUnit :=
4646
let _x.4 := _eval._closed_0.2;

tests/lean/run/erased.lean

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,18 @@ trace: [Compiler.result] size: 1
2525
let _x.1 : PSigma lcErased lcAny := PSigma.mk ◾ ◾ ◾ ◾;
2626
return _x.1
2727
---
28-
trace: [Compiler.result] size: 1
28+
trace: [Compiler.result] size: 5
29+
def _private.lean.run.erased.0._eval._lam_0 (_x.1 : Array
30+
Lean.Name) (_x.2 : PUnit) (_y.3 : Lean.Elab.Term.Context) (_y.4 : lcAny) (_y.5 : Lean.Meta.Context) (_y.6 : lcAny) (_y.7 : Lean.Core.Context) (_y.8 : lcAny) (_y.9 : lcVoid) : EST.Out
31+
Lean.Exception lcAny PUnit :=
32+
let _x.10 : EST.Out Lean.Exception lcAny PUnit := compile _x.1 _y.7 _y.8 _y.9;
33+
cases _x.10 : EST.Out Lean.Exception lcAny PUnit
34+
| EST.Out.ok (a.11 : PUnit) (a.12 : lcVoid) =>
35+
let _x.13 : EST.Out Lean.Exception lcAny PUnit := @EST.Out.ok ◾ ◾ ◾ _x.2 a.12;
36+
return _x.13
37+
| EST.Out.error (a.14 : Lean.Exception) (a.15 : lcVoid) =>
38+
return _x.10
39+
[Compiler.result] size: 1
2940
def _private.lean.run.erased.0._eval._closed_0 : String :=
3041
let _x.1 : String := "Erased";
3142
return _x.1
@@ -50,17 +61,6 @@ trace: [Compiler.result] size: 1
5061
let _x.2 : Array Lean.Name := _eval._closed_3.2;
5162
let _x.3 : Array Lean.Name := Array.push ◾ _x.2 _x.1;
5263
return _x.3
53-
[Compiler.result] size: 5
54-
def _private.lean.run.erased.0._eval._lam_0 (_x.1 : Array
55-
Lean.Name) (_x.2 : PUnit) (_y.3 : Lean.Elab.Term.Context) (_y.4 : lcAny) (_y.5 : Lean.Meta.Context) (_y.6 : lcAny) (_y.7 : Lean.Core.Context) (_y.8 : lcAny) (_y.9 : lcVoid) : EST.Out
56-
Lean.Exception lcAny PUnit :=
57-
let _x.10 : EST.Out Lean.Exception lcAny PUnit := compile _x.1 _y.7 _y.8 _y.9;
58-
cases _x.10 : EST.Out Lean.Exception lcAny PUnit
59-
| EST.Out.ok (a.11 : PUnit) (a.12 : lcVoid) =>
60-
let _x.13 : EST.Out Lean.Exception lcAny PUnit := @EST.Out.ok ◾ ◾ ◾ _x.2 a.12;
61-
return _x.13
62-
| EST.Out.error (a.14 : Lean.Exception) (a.15 : lcVoid) =>
63-
return _x.10
6464
[Compiler.result] size: 9
6565
def _private.lean.run.erased.0._eval (a.1 : Lean.Elab.Command.Context) (a.2 : lcAny) (a.3 : lcVoid) : EST.Out
6666
Lean.Exception lcAny PUnit :=

0 commit comments

Comments
 (0)