refactor: port EmitC to LCNF (#12781)

This PR ports the C emission pass from IR to LCNF, marking the last step
of the IR/LCNF conversion and thus enabling end-to-end code generation
through the new compilation infrastructure.
This commit is contained in:
Henrik Böving
2026-03-11 15:19:54 +01:00
committed by GitHub
parent a32be44f90
commit 652ca9f5b7
14 changed files with 1284 additions and 1056 deletions

View File

@@ -13,7 +13,6 @@ public import Lean.Compiler.IR.CompilerM
public import Lean.Compiler.IR.NormIds
public import Lean.Compiler.IR.Checker
public import Lean.Compiler.IR.UnboxResult
public import Lean.Compiler.IR.EmitC
public import Lean.Compiler.IR.Sorry
public import Lean.Compiler.IR.ToIR
public import Lean.Compiler.IR.ToIRType
@@ -34,7 +33,6 @@ def compile (decls : Array Decl) : CompilerM (Array Decl) := do
let mut decls := decls
decls updateSorryDep decls
logDecls `result decls
checkDecls decls
addDecls decls
inferMeta decls
return decls

View File

@@ -70,6 +70,9 @@ where
decl.saveImpure
let decls Compiler.LCNF.addBoxedVersions #[decl]
let decls Compiler.LCNF.runExplicitRc decls
for decl in decls do
decl.saveImpure
modifyEnv fun env => Compiler.LCNF.recordFinalImpureDecl env decl.name
return decls
addIr (decls : Array (Compiler.LCNF.Decl .impure)) : CoreM Unit := do

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,6 @@ prelude
public import Lean.Compiler.NameMangling
public import Lean.Compiler.IR.EmitUtil
public import Lean.Compiler.IR.NormIds
public import Lean.Compiler.IR.SimpCase
public import Lean.Compiler.IR.LLVMBindings
import Lean.Compiler.LCNF.Types
import Lean.Compiler.ModPkgExt

View File

@@ -1,23 +0,0 @@
/-
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
module
prelude
public import Lean.Compiler.IR.Basic
public section
namespace Lean.IR
def ensureHasDefault (alts : Array Alt) : Array Alt :=
if alts.any Alt.isDefault then alts
else if alts.size < 2 then alts
else
let last := alts.back!
let alts := alts.pop
alts.push (Alt.default last.body)
end Lean.IR

View File

@@ -850,9 +850,11 @@ where
| .jmp .. => inc
| .return .. | unreach .. => return ()
@[inline]
partial def Code.forM [Monad m] (c : Code pu) (f : Code pu m Unit) : m Unit :=
go c
where
@[specialize]
go (c : Code pu) : m Unit := do
f c
match c with

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,56 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Henrik Böving
-/
module
prelude
public import Lean.Compiler.LCNF.CompilerM
import Lean.Compiler.LCNF.PhaseExt
import Lean.Compiler.InitAttr
namespace Lean.Compiler.LCNF
private structure CollectUsedDeclsState where
visited : NameSet := {}
localDecls : Array (Decl .impure) := #[]
extSigs : Array (Signature .impure) := #[]
/--
Find all declarations that the declarations in `decls` transitively depend on. They are returned
partitioned into the declarations from the current module and declarations from other modules.
-/
public partial def collectUsedDecls (decls : Array Name) :
CoreM (Array (Decl .impure) × Array (Signature .impure)) := do
let (_, state) go decls |>.run {}
return (state.localDecls, state.extSigs)
where
go (names : Array Name) : StateRefT CollectUsedDeclsState CoreM Unit :=
names.forM fun name => do
if ( get).visited.contains name then return
modify fun s => { s with visited := s.visited.insert name }
if let some decl getLocalImpureDecl? name then
modify fun s => { s with localDecls := s.localDecls.push decl }
decl.value.forCodeM (·.forM visitCode)
let env getEnv
if let some initializer := getBuiltinInitFnNameFor? env decl.name <|> getInitFnNameFor? env decl.name then
go #[initializer]
else if let some sig getImpureSignature? name then
modify fun s => { s with extSigs := s.extSigs.push sig }
else
panic! s!"collectUsedDecls: could not find declaration or signature for '{name}'"
visitCode (code : Code .impure) : StateRefT CollectUsedDeclsState CoreM Unit := do
match code with
| .let decl _ =>
match decl.value with
| .const declName .. | .fap declName .. | .pap declName .. =>
go #[declName]
| _ => return ()
| _ => return ()
public def usesModuleFrom (env : Environment) (modulePrefix : Name) : Bool :=
env.header.modules.any fun mod => mod.irPhases != .comptime && modulePrefix.isPrefixOf mod.module
end Lean.Compiler.LCNF

View File

@@ -85,7 +85,9 @@ def saveImpure : Pass where
phaseOut := .impure
name := `saveImpure
run decls := decls.mapM fun decl => do
( normalizeFVarIds decl).saveImpure
let decl normalizeFVarIds decl
decl.saveImpure
modifyEnv fun env => recordFinalImpureDecl env decl.name
return decl
shouldAlwaysRunCheck := true
@@ -160,8 +162,8 @@ def builtinPassManager : PassManager := {
pushProj (occurrence := 1),
detectSimpleGround,
inferVisibility (phase := .impure),
saveImpure, -- End of impure phase
toposortPass,
saveImpure, -- End of impure phase
]
}

View File

@@ -23,15 +23,15 @@ namespace Lean.Compiler.LCNF
/--
Set of public declarations whose base bodies should be exported to other modules
-/
private builtin_initialize baseTransparentDeclsExt : EnvExtension (List Name × NameSet) mkDeclSetExt
private builtin_initialize baseTransparentDeclsExt : EnvExtension (List Name × NameSet) mkOrderedDeclSetExt
/--
Set of public declarations whose mono bodies should be exported to other modules
-/
private builtin_initialize monoTransparentDeclsExt : EnvExtension (List Name × NameSet) mkDeclSetExt
private builtin_initialize monoTransparentDeclsExt : EnvExtension (List Name × NameSet) mkOrderedDeclSetExt
/--
Set of public declarations whose impure bodies should be exported to other modules
-/
private builtin_initialize impureTransparentDeclsExt : EnvExtension (List Name × NameSet) mkDeclSetExt
private builtin_initialize impureTransparentDeclsExt : EnvExtension (List Name × NameSet) mkOrderedDeclSetExt
private def getTransparencyExt : Phase EnvExtension (List Name × NameSet)
| .base => baseTransparentDeclsExt
@@ -171,6 +171,9 @@ def getMonoDecl? (declName : Name) : CoreM (Option (Decl .pure)) := do
def getLocalImpureDecl? (declName : Name) : CoreM (Option (Decl .impure)) := do
return impureExt.getState ( getEnv) |>.find? declName
def getLocalImpureDecls : CoreM (Array Name) := do
return impureExt.getState ( getEnv) |>.toArray |>.map (·.fst)
def getImpureSignature? (declName : Name) : CoreM (Option (Signature .impure)) := do
return getSigCore? ( getEnv) impureSigExt declName
@@ -224,4 +227,23 @@ def getLocalDecl? (declName : Name) : CompilerM (Option ((pu : Purity) × Decl p
let some decl getLocalDeclAt? declName ( getPhase) | return none
return some _, decl
builtin_initialize declOrderExt : EnvExtension (List Name × NameSet) mkOrderedDeclSetExt
def recordFinalImpureDecl (env : Environment) (name : Name) : Environment :=
declOrderExt.modifyState env fun s =>
(name :: s.1, s.2.insert name)
def getImpureDeclIndices (env : Environment) (targets : Array Name) : Std.HashMap Name Nat := Id.run do
let (names, set) := declOrderExt.getState env
let mut map := Std.HashMap.emptyWithCapacity set.size
let targetSet := Std.HashSet.ofArray targets
let mut i := set.size
for name in names do
if targetSet.contains name then
map := map.insert name i
assert! i != 0
i := i - 1
assert! map.size == targets.size
return map
end Lean.Compiler.LCNF

View File

@@ -10,15 +10,18 @@ public import Lean.Environment
namespace Lean.Compiler.LCNF
/-- Creates a replayable local environment extension holding a name set. -/
public def mkDeclSetExt : IO (EnvExtension (List Name × NameSet)) :=
/--
Creates a replayable local environment extension holding a name set and the list of names in the
order they were added to the set.
-/
public def mkOrderedDeclSetExt : IO (EnvExtension (List Name × NameSet)) :=
registerEnvExtension
(mkInitial := pure ([], {}))
(asyncMode := .sync)
(replay? := some <| fun oldState newState _ s =>
let newEntries := newState.1.take (newState.1.length - oldState.1.length)
newEntries.foldl (init := s) fun s n =>
if s.1.contains n then
newEntries.reverse.foldl (init := s) fun s n =>
if s.2.contains n then
s
else
(n :: s.1, if newState.2.contains n then s.2.insert n else s.2))
@@ -26,7 +29,7 @@ public def mkDeclSetExt : IO (EnvExtension (List Name × NameSet)) :=
/--
Set of declarations to be exported to other modules; visibility shared by base/mono/IR phases.
-/
private builtin_initialize publicDeclsExt : EnvExtension (List Name × NameSet) mkDeclSetExt
private builtin_initialize publicDeclsExt : EnvExtension (List Name × NameSet) mkOrderedDeclSetExt
public def isDeclPublic (env : Environment) (declName : Name) : Bool := Id.run do
if !env.header.isModule then

View File

@@ -115,6 +115,16 @@ def Decl.simpCase (decl : Decl .impure) : CompilerM (Decl .impure) := do
let value decl.value.mapCodeM (·.simpCase)
return { decl with value }
public def ensureHasDefault (alts : Array (Alt .impure)) : Array (Alt .impure) :=
if alts.any (· matches .default ..) then
alts
else if alts.size < 2 then
alts
else
let last := alts.back!
let alts := alts.pop
alts.push (.default last.getCode)
public def simpCase : Pass :=
Pass.mkPerDeclaration `simpCase .impure Decl.simpCase 0

View File

@@ -8,6 +8,7 @@ module
prelude
public import Lean.Compiler.LCNF.CompilerM
public import Lean.Compiler.LCNF.PassManager
import Lean.Compiler.InitAttr
/-!
This module "topologically sorts" an SCC of decls (an SCC of decls in the pipeline may in fact
@@ -42,8 +43,11 @@ where
if ( get).seen.contains decl.name then
return ()
let env getEnv
modify fun s => { s with seen := s.seen.insert decl.name }
decl.value.forCodeM (·.forM visitConsts)
if let some initializer := getBuiltinInitFnNameFor? env decl.name <|> getInitFnNameFor? env decl.name then
visitConst initializer
modify fun s => { s with order := s.order.push decl }
visitConsts (code : Code pu) : ToposortM pu Unit := do
@@ -51,15 +55,16 @@ where
| .let decl _ =>
match decl.value with
| .const declName .. | .fap declName .. | .pap declName .. =>
if let some d := ( read).declsMap[declName]? then
process d
visitConst declName
| _ => return ()
| _ => return ()
visitConst (declName : Name) : ToposortM pu Unit := do
if let some d := ( read).declsMap[declName]? then
process d
public def toposortDecls (decls : Array (Decl pu)) : CompilerM (Array (Decl pu)) := do
let (externDecls, otherDecls) := decls.partition (fun decl => decl.value matches .extern ..)
let otherDecls toposort otherDecls
return externDecls ++ otherDecls
toposort decls
public def toposortPass : Pass where
phase := .impure

View File

@@ -10,7 +10,7 @@ import Lean.Elab.Frontend
import Lean.Elab.ParseImportsFast
import Lean.Server.Watchdog
import Lean.Server.FileWorker
import Lean.Compiler.IR.EmitC
import Lean.Compiler.LCNF.EmitC
import Init.System.Platform
/- Lean companion to `shell.cpp` -/
@@ -545,7 +545,8 @@ def shellMain (args : List String) (opts : ShellOptions) : IO UInt32 := do
| IO.eprintln s!"failed to create '{c}'"
return 1
profileitIO "C code generation" opts.leanOpts do
let data IR.emitC env mainModuleName
let data Compiler.LCNF.emitC mainModuleName
|>.toIO' { fileName, fileMap := default } { env }
out.write data.toUTF8
if let some bc := opts.bcFileName? then
initLLVM