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
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
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