mirror of
https://github.com/anoma/juvix.git
synced 2024-09-11 16:26:33 +03:00
Currently formatting a project is equivalent to running `juvix format`
on each individual file. Hence, the performance is quadratic wrt the
number of modules in the project. This pr fixes that and we now we only
process each module once.
# Benchmark (1236% faster 🚀)
Checking the standard library
```
hyperfine --warmup 1 'juvix format --check' 'juvix-main format --check'
Benchmark 1: juvix format --check
Time (mean ± σ): 450.6 ms ± 33.7 ms [User: 707.2 ms, System: 178.7 ms]
Range (min … max): 396.0 ms … 497.0 ms 10 runs
Benchmark 2: juvix-main format --check
Time (mean ± σ): 6.019 s ± 0.267 s [User: 9.333 s, System: 1.512 s]
Range (min … max): 5.598 s … 6.524 s 10 runs
Summary
juvix format --check ran
13.36 ± 1.16 times faster than juvix-main format --check
```
# Other changes:
1. The `EntryPoint` field `entryPointModulePath` is now optional.
2. I've introduced a new type `TopModulePathKey` which is analogous to
`TopModulePath` but wihout location information. It is used in hashmap
keys where the location in the key is never used. This is useful as we
can now get a `TopModulePathKey` from a `Path Rel File`.
3. I've refactored the `_formatInput` field in `FormatOptions` so that
it doesn't need to be a special case anymore.
4. I've introduced a new effect `Forcing` that allows to individually
force fields of a record type with a convenient syntax.
5. I've refactored some of the constraints in scoping so that they only
require `Reader Package` instead of `Reader EntryPoint`.
6. I've introduced a new type family so that local modules are no longer
required to have `ModuleId` from their type. Before, they were assigned
one, but it was never used.
# Future work:
1. For project-wise formatting, the compilation is done in parallel, but
the formatting is still done sequentially. That should be improved.
226 lines
7.1 KiB
Haskell
226 lines
7.1 KiB
Haskell
module GlobalOptions
|
|
( module GlobalOptions,
|
|
module Juvix.Data.Effect.TaggedLock,
|
|
)
|
|
where
|
|
|
|
import CommonOptions
|
|
import Juvix.Compiler.Core.Options qualified as Core
|
|
import Juvix.Compiler.Internal.Pretty.Options qualified as Internal
|
|
import Juvix.Compiler.Pipeline
|
|
import Juvix.Compiler.Pipeline.Root
|
|
import Juvix.Data.Effect.TaggedLock
|
|
import Juvix.Data.Error.GenericError qualified as E
|
|
import Juvix.Data.Field
|
|
|
|
data GlobalOptions = GlobalOptions
|
|
{ _globalNoColors :: Bool,
|
|
_globalShowNameIds :: Bool,
|
|
_globalBuildDir :: Maybe (AppPath Dir),
|
|
_globalOnlyErrors :: Bool,
|
|
_globalStdin :: Bool,
|
|
_globalNoTermination :: Bool,
|
|
_globalNoPositivity :: Bool,
|
|
_globalNoCoverage :: Bool,
|
|
_globalNoStdlib :: Bool,
|
|
_globalUnrollLimit :: Int,
|
|
_globalNumThreads :: NumThreads,
|
|
_globalFieldSize :: Maybe Natural,
|
|
_globalOffline :: Bool,
|
|
_globalDevShowThreadIds :: Bool
|
|
}
|
|
deriving stock (Eq, Show)
|
|
|
|
makeLenses ''GlobalOptions
|
|
|
|
instance CanonicalProjection GlobalOptions Internal.Options where
|
|
project g =
|
|
Internal.defaultOptions
|
|
{ Internal._optShowNameIds = g ^. globalShowNameIds
|
|
}
|
|
|
|
instance CanonicalProjection GlobalOptions E.GenericOptions where
|
|
project GlobalOptions {..} =
|
|
E.GenericOptions
|
|
{ E._showNameIds = _globalShowNameIds
|
|
}
|
|
|
|
instance CanonicalProjection GlobalOptions Core.CoreOptions where
|
|
project GlobalOptions {..} =
|
|
Core.CoreOptions
|
|
{ Core._optCheckCoverage = not _globalNoCoverage,
|
|
Core._optUnrollLimit = _globalUnrollLimit,
|
|
Core._optFieldSize = fromMaybe defaultFieldSize _globalFieldSize,
|
|
Core._optOptimizationLevel = defaultOptimizationLevel,
|
|
Core._optInliningDepth = defaultInliningDepth
|
|
}
|
|
|
|
defaultGlobalOptions :: GlobalOptions
|
|
defaultGlobalOptions =
|
|
GlobalOptions
|
|
{ _globalNoColors = False,
|
|
_globalNumThreads = defaultNumThreads,
|
|
_globalShowNameIds = False,
|
|
_globalOnlyErrors = False,
|
|
_globalNoTermination = False,
|
|
_globalBuildDir = Nothing,
|
|
_globalStdin = False,
|
|
_globalNoPositivity = False,
|
|
_globalNoCoverage = False,
|
|
_globalNoStdlib = False,
|
|
_globalUnrollLimit = defaultUnrollLimit,
|
|
_globalFieldSize = Nothing,
|
|
_globalDevShowThreadIds = False,
|
|
_globalOffline = False
|
|
}
|
|
|
|
-- | Get a parser for global flags which can be hidden or not depending on
|
|
-- the input boolean
|
|
parseGlobalFlags :: Parser GlobalOptions
|
|
parseGlobalFlags = do
|
|
_globalNoColors <-
|
|
switch
|
|
( long "no-colors"
|
|
<> help "Disable ANSI formatting"
|
|
)
|
|
_globalBuildDir <-
|
|
optional
|
|
( parseBuildDir
|
|
( long "internal-build-dir"
|
|
<> help "Directory for compiler internal output"
|
|
)
|
|
)
|
|
_globalStdin <-
|
|
switch
|
|
( long "stdin"
|
|
<> help "Read from Stdin"
|
|
)
|
|
_globalOnlyErrors <-
|
|
switch
|
|
( long "only-errors"
|
|
<> help "Only print errors in a uniform format (used by juvix-mode)"
|
|
)
|
|
_globalNoTermination <-
|
|
switch
|
|
( long "no-termination"
|
|
<> help "Disable termination checking"
|
|
)
|
|
_globalNoPositivity <-
|
|
switch
|
|
( long "no-positivity"
|
|
<> help "Disable positivity checking for inductive types"
|
|
)
|
|
_globalNoCoverage <-
|
|
switch
|
|
( long "no-coverage"
|
|
<> help "Disable coverage checking for patterns"
|
|
)
|
|
_globalNoStdlib <-
|
|
switch
|
|
( long "no-stdlib"
|
|
<> help "Do not use the standard library"
|
|
)
|
|
_globalFieldSize <-
|
|
option
|
|
fieldSizeOpt
|
|
( long "field-size"
|
|
<> value Nothing
|
|
<> help "Field type size [cairo,small,11] (default: small)"
|
|
)
|
|
_globalUnrollLimit <-
|
|
option
|
|
(fromIntegral <$> naturalNumberOpt)
|
|
( long "unroll"
|
|
<> value defaultUnrollLimit
|
|
<> help ("Recursion unrolling limit (default: " <> show defaultUnrollLimit <> ")")
|
|
)
|
|
_globalOffline <-
|
|
switch
|
|
( long "offline"
|
|
<> help "Disable access to network resources"
|
|
)
|
|
_globalShowNameIds <-
|
|
switch
|
|
( long "show-name-ids"
|
|
<> help "[DEV] Show the unique number of each identifier when pretty printing"
|
|
)
|
|
_globalDevShowThreadIds <-
|
|
switch
|
|
( long "dev-show-thread-ids"
|
|
<> help "[DEV] Show the thread id when compiling a module"
|
|
)
|
|
_globalNumThreads <- parseNumThreads
|
|
return GlobalOptions {..}
|
|
|
|
parseBuildDir :: Mod OptionFields (Prepath Dir) -> Parser (AppPath Dir)
|
|
parseBuildDir m = do
|
|
_pathPath <-
|
|
option
|
|
somePreDirOpt
|
|
( metavar "BUILD_DIR"
|
|
<> action "directory"
|
|
<> m
|
|
)
|
|
pure AppPath {_pathIsInput = False, ..}
|
|
|
|
entryPointFromGlobalOptionsPre ::
|
|
(Members '[TaggedLock, EmbedIO] r) =>
|
|
Root ->
|
|
Maybe (Prepath File) ->
|
|
GlobalOptions ->
|
|
Sem r EntryPoint
|
|
entryPointFromGlobalOptionsPre root premainFile opts = do
|
|
mainFile <- mapM (prepathToAbsFile (root ^. rootInvokeDir)) premainFile
|
|
entryPointFromGlobalOptions root mainFile opts
|
|
|
|
entryPointFromGlobalOptions ::
|
|
(Members '[TaggedLock, EmbedIO] r) =>
|
|
Root ->
|
|
Maybe (Path Abs File) ->
|
|
GlobalOptions ->
|
|
Sem r EntryPoint
|
|
entryPointFromGlobalOptions root mainFile opts = do
|
|
mabsBuildDir :: Maybe (Path Abs Dir) <- liftIO (mapM (prepathToAbsDir cwd) optBuildDir)
|
|
pkg <- readPackageRootIO root
|
|
let def :: EntryPoint
|
|
def = defaultEntryPoint pkg root mainFile
|
|
return
|
|
def
|
|
{ _entryPointNoTermination = opts ^. globalNoTermination,
|
|
_entryPointNoPositivity = opts ^. globalNoPositivity,
|
|
_entryPointNoCoverage = opts ^. globalNoCoverage,
|
|
_entryPointNoStdlib = opts ^. globalNoStdlib,
|
|
_entryPointUnrollLimit = opts ^. globalUnrollLimit,
|
|
_entryPointGenericOptions = project opts,
|
|
_entryPointBuildDir = maybe (def ^. entryPointBuildDir) (CustomBuildDir . Abs) mabsBuildDir,
|
|
_entryPointOffline = opts ^. globalOffline,
|
|
_entryPointFieldSize = fromMaybe defaultFieldSize $ opts ^. globalFieldSize
|
|
}
|
|
where
|
|
optBuildDir :: Maybe (Prepath Dir)
|
|
optBuildDir = fmap (^. pathPath) (opts ^. globalBuildDir)
|
|
cwd = root ^. rootInvokeDir
|
|
|
|
entryPointFromGlobalOptionsNoFile :: (Members '[EmbedIO, TaggedLock] r, MonadIO (Sem r)) => Root -> GlobalOptions -> Sem r EntryPoint
|
|
entryPointFromGlobalOptionsNoFile root opts = do
|
|
mabsBuildDir :: Maybe (Path Abs Dir) <- mapM (prepathToAbsDir cwd) optBuildDir
|
|
pkg <- readPackageRootIO root
|
|
let def :: EntryPoint
|
|
def = defaultEntryPointNoFile pkg root
|
|
return
|
|
def
|
|
{ _entryPointNoTermination = opts ^. globalNoTermination,
|
|
_entryPointNoPositivity = opts ^. globalNoPositivity,
|
|
_entryPointNoCoverage = opts ^. globalNoCoverage,
|
|
_entryPointNoStdlib = opts ^. globalNoStdlib,
|
|
_entryPointUnrollLimit = opts ^. globalUnrollLimit,
|
|
_entryPointGenericOptions = project opts,
|
|
_entryPointBuildDir = maybe (def ^. entryPointBuildDir) (CustomBuildDir . Abs) mabsBuildDir,
|
|
_entryPointOffline = opts ^. globalOffline,
|
|
_entryPointFieldSize = fromMaybe defaultFieldSize $ opts ^. globalFieldSize
|
|
}
|
|
where
|
|
optBuildDir :: Maybe (Prepath Dir)
|
|
optBuildDir = fmap (^. pathPath) (opts ^. globalBuildDir)
|
|
cwd = root ^. rootInvokeDir
|