Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
**/zig-cache
**/.acton.compile.lock
**/.acton.lock
**/.acton.output.lock
**/.build/
**/*.swp
**/*.ty
Expand Down Expand Up @@ -99,5 +100,6 @@ snapshots/output/**
# tests organized as acton projects; ignore all but source
test/**/out/**
test/**/.acton.lock
test/**/.acton.output.lock
compiler/acton/test/project/*/out
compiler/acton/test/root/test
149 changes: 113 additions & 36 deletions compiler/acton/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import Acton.Printer ()
import qualified Acton.Env
import Acton.Env (simp, define, setMod)
import qualified Acton.QuickType
import qualified Acton.ReachabilityPrinter as ReachabilityPrinter
import qualified Acton.ReachabilityRows as ReachRows
import qualified Acton.Kinds
import qualified Acton.Types
import qualified Acton.Solver
Expand All @@ -40,6 +42,7 @@ import qualified Acton.Builtin
import qualified Acton.DocPrinter as DocP
import qualified Acton.Diagnostics as Diag
import qualified Acton.Fingerprint as Fingerprint
import qualified Acton.Hashing as Hashing
import qualified Acton.SourceProvider as Source
import Acton.Compile
import Utils
Expand Down Expand Up @@ -129,6 +132,9 @@ raiseOpenFileLimit =
setResourceLimit ResourceOpenFiles lim{ softLimit = ResourceLimit target }

main = do
-- Force the executable identity before compilation workers can query it.
-- Hashing memoizes the result for all later code-generation keys.
_ <- evaluate Hashing.codegenIdentity
raiseOpenFileLimit
hSetBuffering stdout LineBuffering
arg <- C.parseCmdLine
Expand Down Expand Up @@ -955,15 +961,20 @@ fetchCommand gopts = do
putStrLn "Dependencies fetched"

data SigTarget
= SigSourceTarget ProjCtx A.ModName (Maybe A.Name) FilePath
= SigProjectTarget
| SigSourceTarget ProjCtx A.ModName (Maybe A.Name) FilePath
| SigTyTarget A.ModName (Maybe A.Name) FilePath

sigCommand :: C.GlobalOptions -> C.SigOptions -> IO ()
sigCommand gopts sigOpts = do
let projectTarget = maybe True (== ".") (C.sigTarget sigOpts)
when (projectTarget && not (C.sigReachability sigOpts)) $
printErrorAndExit "Signature target required; use --reachability for the project"
let opts0 = (C.sigCompile sigOpts)
{ C.skip_build = True
, C.only_build = False
, C.sigs = False
, C.no_dbp = not projectTarget
}
queryGopts = if C.verbose gopts then gopts else gopts { C.quiet = True }
paths0 <- loadProjectPaths opts0
Expand All @@ -974,39 +985,66 @@ sigCommand gopts sigOpts = do
sysAbs <- normalizePathSafe (sysPath paths)
withProjectLockNotice queryGopts rootProj $ do
fetchDependencies queryGopts paths depOverrides
projMap <- discoverProjects queryGopts sysAbs rootProj depOverrides
target <- resolveSigTarget opts paths rootProj projMap (C.sigTarget sigOpts)
tyFile <- case target of
SigSourceTarget ctx mn _ srcPath ->
compileSigTarget gopts queryGopts opts paths rootProj sysAbs depOverrides ctx mn srcPath
SigTyTarget _ _ tyPath ->
return tyPath
case target of
SigSourceTarget _ mn mName _ -> printSigInterface paths mn mName tyFile
SigTyTarget mn mName _ -> printSigInterface paths mn mName tyFile
buildStamp <- readBuildSpecStamp (projPath paths)
let compileCtx = CompileContext
{ ccOpts = opts
, ccDepOverrides = depOverrides
, ccPathsRoot = paths
, ccRootProj = rootProj
, ccSysAbs = sysAbs
, ccBuildStamp = buildStamp
}
lockPaths <- compileOutputLockPaths queryGopts compileCtx
withCompileOutputLockPaths lockPaths $ do
target <- case C.sigTarget sigOpts of
Nothing -> return SigProjectTarget
Just "." -> return SigProjectTarget
Just rawTarget -> do
projMap <- discoverProjects queryGopts sysAbs rootProj depOverrides
resolveSigTarget opts paths rootProj projMap rawTarget
case target of
SigProjectTarget ->
printProjectReachability gopts queryGopts compileCtx paths
SigSourceTarget ctx mn mName srcPath -> do
tyFile <- compileSigTarget gopts queryGopts compileCtx ctx mn srcPath
printSigResult (C.sigReachability sigOpts) paths mn mName tyFile
SigTyTarget mn mName tyFile ->
printSigResult (C.sigReachability sigOpts) paths mn mName tyFile

printProjectReachability :: C.GlobalOptions
-> C.GlobalOptions
-> CompileContext
-> Paths
-> IO ()
printProjectReachability gopts queryGopts cctx paths = do
let sp = Source.diskSourceProvider
srcFiles <- projectSourceFiles paths
plan <- prepareCompilePlanFromContext sp queryGopts cctx srcFiles True Nothing
let cctx' = cpContext plan
opts' = ccOpts cctx'
callbacks = defaultCompileCallbacks
{ ccOnDiagnostics = \_ optsT diags -> printDiagnostics gopts optsT diags
, ccOnInfo = \msg -> when (C.verbose gopts) $ putStrLn msg
, ccOnBackJob = \_ -> return ()
, ccOnReachability = \whole selection ->
putStrLn (ReachabilityPrinter.prettySelection whole selection)
}
compileRes <- compileTasks sp queryGopts opts' (ccPathsRoot cctx') (ccRootProj cctx')
(cpRootTaskKeys plan) (cpRequestedTasks plan)
(cpNeededTasks plan) (cpDbpBlocked plan) callbacks
case compileRes of
Left err -> printErrorAndExit (compileFailureMessage err)
Right (_, hadErrors) -> when hadErrors System.Exit.exitFailure

compileSigTarget :: C.GlobalOptions
-> C.GlobalOptions
-> C.CompileOptions
-> Paths
-> FilePath
-> FilePath
-> [(String, FilePath)]
-> CompileContext
-> ProjCtx
-> A.ModName
-> FilePath
-> IO FilePath
compileSigTarget gopts queryGopts opts paths rootProj sysAbs depOverrides targetCtx mn srcPath = do
buildStamp <- readBuildSpecStamp (projPath paths)
compileSigTarget gopts queryGopts cctx targetCtx mn srcPath = do
let sp = Source.diskSourceProvider
cctx = CompileContext
{ ccOpts = opts
, ccDepOverrides = depOverrides
, ccPathsRoot = paths
, ccRootProj = rootProj
, ccSysAbs = sysAbs
, ccBuildStamp = buildStamp
}
plan <- prepareCompilePlanFromContext sp queryGopts cctx [srcPath] False Nothing
let cctx' = cpContext plan
opts' = ccOpts cctx'
Expand All @@ -1015,7 +1053,9 @@ compileSigTarget gopts queryGopts opts paths rootProj sysAbs depOverrides target
, ccOnInfo = \msg -> when (C.verbose gopts) $ putStrLn msg
, ccOnBackJob = \_ -> return ()
}
compileRes <- compileTasks sp queryGopts opts' (ccPathsRoot cctx') (ccRootProj cctx') (cpNeededTasks plan) (cpDbpBlocked plan) callbacks
compileRes <- compileTasks sp queryGopts opts' (ccPathsRoot cctx') (ccRootProj cctx')
(cpRootTaskKeys plan) (cpRequestedTasks plan)
(cpNeededTasks plan) (cpDbpBlocked plan) callbacks
case compileRes of
Left err -> printErrorAndExit (compileFailureMessage err)
Right (_, hadErrors) -> when hadErrors System.Exit.exitFailure
Expand Down Expand Up @@ -1143,6 +1183,28 @@ printSigInterface paths mn mName tyFile = do
_ ->
putStrLn (Acton.Types.prettySigs envForPrint mn (map dropPrefix imps) (Acton.Names.nmap dropPrefix selected))

printSigResult :: Bool -> Paths -> A.ModName -> Maybe A.Name -> FilePath -> IO ()
printSigResult showReachability paths mn mName tyFile
| showReachability = printSigReachability mn mName tyFile
| otherwise = printSigInterface paths mn mName tyFile

printSigReachability :: A.ModName -> Maybe A.Name -> FilePath -> IO ()
printSigReachability mn mName tyFile = do
exists <- InterfaceFiles.interfaceExists tyFile
unless exists $
printErrorAndExit ("Type interface not found for " ++ modNameToString mn)
rowsRes <- try (InterfaceFiles.readReachabilityRows tyFile mn mName)
:: IO (Either SomeException ReachRows.ReachabilityRows)
case rowsRes of
Left err ->
printErrorAndExit
("Could not read reachability for " ++ modNameToString mn ++ ": " ++ show err)
Right rows -> case mName of
Just name | M.null (ReachRows.reachTopRows rows) ->
printErrorAndExit
("Name not found: " ++ modNameToString mn ++ "." ++ nameToString name)
_ -> putStrLn (ReachabilityPrinter.prettyRows mn mName rows)

-- Show dependency tree with overrides applied from root pins
pkgShow :: C.GlobalOptions -> IO ()
pkgShow gopts = do
Expand Down Expand Up @@ -1398,8 +1460,6 @@ compileFilesChanged sp gopts opts srcFiles allowPrune mChangedPaths mSched mProg
cleanupProgress
let runCompile = do
sp' <- overlayChangedPaths sp mChangedPaths
planRes <- try $
prepareCompilePlan sp' gopts sched opts srcFiles allowPrune mChangedPaths
let reportPlanError (ProjectError msg) = do
if C.watch opts
then logLine msg
Expand All @@ -1423,7 +1483,7 @@ compileFilesChanged sp gopts opts srcFiles allowPrune mChangedPaths mSched mProg
reportCompileErrors =
finalizeCompile $
unless watchMode System.Exit.exitFailure
compileRes <- runCompilePlan sp gopts plan sched gen (cchHooks cliHooks)
compileRes <- runCompilePlan sp' gopts plan sched gen (cchHooks cliHooks)
case compileRes of
Left err ->
reportCompileError (compileFailureMessage err)
Expand All @@ -1442,7 +1502,18 @@ compileFilesChanged sp gopts opts srcFiles allowPrune mChangedPaths mSched mProg
clearProgress
whenCurrentGen sched gen (runCliPostCompile cliHooks gopts plan env)
return False
either reportPlanError runPlan planRes
planRun <- try $ do
ctx <- prepareCompileContext opts srcFiles
specChanged <- checkBuildSpecChange sched (ccBuildStamp ctx)
when specChanged $
fetchDependencies gopts (ccPathsRoot ctx) (ccDepOverrides ctx)
lockPaths <- compileOutputLockPaths gopts ctx
withCompileOutputLockPaths lockPaths $ do
let changed = if specChanged then Nothing else mChangedPaths
plan <- prepareCompilePlanFromContext
sp' gopts ctx srcFiles allowPrune changed
runPlan plan
either reportPlanError return planRun
runCompile `finally` cleanupProgress

overlayChangedPaths :: Source.SourceProvider -> Maybe [FilePath] -> IO Source.SourceProvider
Expand Down Expand Up @@ -2288,9 +2359,15 @@ pubHash, and a downstream module only needs front passes when a pubHash changes.

Implementation hashing: each top-level name gets an implHash computed from its
source hash plus the impl hashes of its dependencies. The moduleImplHash is the
hash of all per-name impl hashes. We embed moduleImplHash into generated .c/.h
files so we can skip back passes when codegen is already up to date, and we use
it (with impl deps) to drive the test cache.
hash of all per-name impl hashes and, together with impl dependencies, drives
back-pass refreshes and the test cache.

Code-generation hashing: generated .c/.h files carry the exact hash of the back
passes that produced them. A whole-module back pass combines moduleImplHash
with the source bytes, compiler identity, and line-directive mode. A selective
back pass instead hashes the selected tops, members, ABI declarations, and their
captured interface universe, so unselected implementation is absent from the
key. A mismatch reruns back passes without rerunning front passes.

Terminology
- ParseTask: a source-backed module whose imports are known but whose full AST
Expand Down Expand Up @@ -2342,8 +2419,8 @@ High-level Steps
- Otherwise, compare each used impl dependency hash from the dependent’s
.tydb header with the provider’s current impl hash. If any differ → refresh
impl hashes and run back passes.
- Otherwise, if generated .c/.h hashes do not match moduleImplHash → run
back passes.
- Otherwise, if generated .c/.h hashes do not match the expected codegen
hash → run back passes.
- Otherwise → module is fresh (no work).
- We maintain a pubMap while walking modules in topological order. After a
module compiles, we insert its freshly computed public hash; when a
Expand Down
Loading
Loading