blob: 8f4f52e556dc158e59ae8df2f17712a181aefb27 [file] [edit]
/-
Copyright (c) 2025 Lean FRO. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sebastian Ullrich
-/
module
import Lean.CoreM
import Lean.Util.ForEachExpr
import all Lean.Util.Path
import all Lean.Environment
import Lean.Compiler.Options
import Lean.Compiler.IR.CompilerM
import Lean.Compiler.ModPkgExt
import all Lean.Compiler.CSimpAttr
import Lean.Compiler.LCNF.EmitC
import Lean.Language.Lean
import Lean.Compiler.LCNF.PhaseExt
import Lean.Compiler.LCNF.Main
-- `leanir` uses `lean_initialize_minimal`, which leaves initializing the `Lean`
-- modules to this module's import closure. These are the modules defining an `@[export]` whose
-- `@[extern]` counterpart is in that closure but which the closure does not otherwise reach.
import Lean.Meta.ExprDefEq
import Lean.Meta.LevelDefEq
import Lean.Meta.Match.MatchEqs
import Lean.Elab.PreDefinition.Structural.Eqns
import Lean.Parser
/-! Lean codegen as a separate process. -/
open Lean Compiler LCNF
/-- Leaner alternative to `.ir` for non-`import all` compilation. -/
def mkIRSigData (env : Environment) : IO ModuleData := do
let data ← mkModuleData env .exported
return { data with
extraConstNames := getIRExtraConstNames env .exported
}
def mkIRData (env : Environment) : IO ModuleData := do
-- TODO: should we use a more specific/efficient data format for IR?
-- `exportIREntries` provides full IR for `declMapExt` and `regularInitAttr`; filter them from
-- `mkModuleData` to prevent the latter's opaque-extern entries from overwriting the full IR
-- in `setImportedEntries`
let irEntries := exportIREntries env
let irExtNames := irEntries.map (·.1)
let modEntries := (← mkModuleData env .private).entries.filter (!irExtNames.contains ·.1)
return { env.header with
entries := irEntries ++ modEntries
constants := default
constNames := default
-- make sure to include all names in case only `.ir` is loaded
-- TODO: `.private` because `import all` may require otherwise unreachable IR entries
extraConstNames := getIRExtraConstNames env .private (includeDecls := true)
}
def setConfigOption (opts : Options) (arg : String) : IO Options := do
let some arg := arg.dropPrefix? "-D" |
throw <| .userError s!"invalid trailing argument `{arg}`, expected argument of the form `-Dopt=val`"
let pos := arg.find '='
if h : pos.IsAtEnd then
throw <| .userError "invalid -D parameter, argument must contain '='"
else
let name := arg.sliceTo pos |>.toName
let val := arg.sliceFrom (pos.next h) |>.copy
if let some decl := (← getOptionDecls).find? name then
Language.Lean.setOption opts decl name val
else
throw <| .userError s!"unknown option '{name}'"
public def main (args : List String) : IO UInt32 := do
let setupFile::irFile::c::optArgs := args | do
IO.println s!"usage: leanir <setup.json> <output.ir> <output.c> [--stat] <-Dopt=val>..."
return 1
let setup ← ModuleSetup.load setupFile
let modName := setup.name
let mut printStats := false
let mut opts := setup.options.toOptions
for optArg in optArgs do
if optArg == "--stat" then
printStats := true
else
opts ← setConfigOption opts optArg
opts := Compiler.compiler.inLeanIR.set opts true
opts := maxHeartbeats.set opts 0
initSearchPathInternal
-- Provide access to private scope of target module but no others; provide all IR
let env ← profileitIO "import" opts <| withImporting do
-- `importAll` so we have access to all private data
let imports := #[{ module := modName, importAll := true : Import }]
let (_, s) ← importModulesCore (globalLevel := .exported) (loadIRSig := true)
(arts := setup.importArts) imports |>.run
let s := { s with moduleNameMap := s.moduleNameMap.modify modName fun m => { m with irPhases := .runtime } }
-- level exported because otherwise we would try to load the current module's `.ir`
finalizeImport (leakEnv := true) (loadExts := false) (level := .exported) (loadIRSig := true) s imports opts
let env := env.setMainModule modName
let env := env.setModulePackage setup.package?
let initExt {α β σ} [Inhabited σ] (ext : PersistentEnvExtension α β σ) (env : Environment) : IO Environment := do
let s := ext.toEnvExtension.getState env
let newState ← ext.addImportedFn s.importedEntries { env := env, opts := {} }
return ext.toEnvExtension.setState (asyncMode := .sync) env { s with state := newState }
let env ← initExt Lean.Compiler.CSimp.ext.ext env
let env ← initExt Meta.instanceExtension.ext env
let env ← initExt classExtension env
let env ← initExt Meta.Match.Extension.extension env
let some modIdx := env.getModuleIdx? modName
| throw <| IO.userError s!"module '{modName}' not found"
let decls := impureSigExt.getModuleEntries env modIdx
let decls := decls.filter (isExtern env ·.name)
let env := decls.foldl (fun env decl => impureSigExt.addEntry env decl) env
let env := decls.foldl (fun env decl => setDeclPublic env decl.name) env
-- Fill `declMapExt` with functions compiled already in `lean` so the set of "local" decls is
-- unchanged and also for calculation of `extraConstNames` above
-- TODO: we do manually-added externs only as others need more state sync around ground exprs etc
let is := Lean.IR.declMapExt.toEnvExtension.getState env
let unbox : Name → Name
| .str f "_boxed" => f
| f => f
let newState := is.importedEntries[modIdx]!.foldl (fun (decls, m) d => if isExtern env (unbox d.name) then (d::decls, m.insert d.name d) else (decls, m)) is.state
let env := Lean.IR.declMapExt.toEnvExtension.setState (asyncMode := .sync) env { is with state := newState }
let some mod := env.header.moduleData[modIdx]? | unreachable!
-- Make sure we record the actual IR dependencies, not ourselves
let env := { env with base.private.header.imports := mod.imports }
let _ : MonadExceptOf _ CoreM := MonadAlwaysExcept.except
let res? ← EIO.toBaseIO <| Core.CoreM.run (ctx := { fileName := irFile, fileMap := default, options := opts })
(s := { env }) try
let decls := postponedCompileDeclsExt.getModuleEntries env modIdx
-- Load postponed decls and iterate over them, removing each entry to avoid infinite recursion.
-- NOTE: `decls` is in the original order of declarations so initializing and managing the
-- extension would not be necessary for that, we could just leave it empty. However, there are
-- cases like a `[csimp]` replacement being defined below the original function in the same file
-- where allowing `LCNF.main` to reorder compilation based on dependencies seems to be the
-- simpler solution (as we may make the csimp replacement before the replacement has been
-- compiled).
modifyEnv (postponedCompileDeclsExt.setState · (decls.foldl (fun s e => e.declNames.foldl (·.insert · e) s) {}))
for decl in decls do
for decl in decl.declNames do
try
resumeCompilation decl (← getOptions)
finally
addTraceAsMessages
for msg in (← Core.getAndEmptyMessageLog).unreported do
IO.eprintln (← msg.toString)
catch e =>
unless e.isInterrupt do
logError e.toMessageData
let .ok (_, s) := res? | unreachable!
let mut env := s.env
for msg in s.messages.unreported do
IO.eprintln (← msg.toString)
if s.messages.hasErrors then
return 1
-- Save, basing `.ir` on top of `.ir.sig`
let irSigFile := (irFile : System.FilePath).addExtension "sig"
saveModuleDataParts (env.mainModule ++ `ir) #[
(irSigFile, ← mkIRSigData env),
(irFile, ← mkIRData env)]
let .ok out ← IO.FS.Handle.mk c .write |>.toBaseIO
| IO.eprintln s!"failed to create '{c}'"
return 1
profileitIO "C code generation" opts do
let data ← Compiler.LCNF.emitC modName
|>.toIO' { fileName := irFile, fileMap := default } { env }
out.write data.toUTF8
displayCumulativeProfilingTimes
if printStats then
env.displayStats
return 0