Skip to content

Conversation

@alexfmpe
Copy link
Contributor

@alexfmpe alexfmpe commented Apr 25, 2025

This is a first batch of changes towards explicitly modeling the data dependencies so it's easier to reason about, find saving opportunities and offload cognitive load to the compiler.

Before

$ time cabal run hie-bios -- debug hie-bios.cabal
real	0m0.710s

$ cabal test all
All 43 tests passed (56.80s)

After

$ time cabal run hie-bios -- debug hie-bios.cabal
real	0m0.635s

$ cabal test all
All 43 tests passed (55.25s)

When working on #458 I often had trouble reasoning about and keeping track of the actual flow due to the MonadFix IO used for lazy reading of program versions.
In particular, the first ghc version check triggers a cabal version check, so trying to read the ghc version from cabal path not only doesn't save any time (since we're preparing a ghc call for it), as it will also cause an infinite cycle if we try to read the ghc version lookup in here. The real fun starts when this is triggered by touching something else that's unwittingly involved in the cycle.

@alexfmpe alexfmpe force-pushed the cabal-path-correctness branch from 00c31c8 to a7a9fed Compare April 26, 2025 00:17
deriving (Functor, Foldable, Traversable)

makeVersions :: LogAction IO (WithSeverity Log) -> FilePath -> IO BuildToolVersions
makeVersions l wdir = mapConcurrently (\v -> v l wdir) $ BuildToolVersions getCabalVersion getStackVersion
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we use mapConcurrently here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Welp, forgot to click submit on my own review back when opening the PR.

why do we use mapConcurrently here?

To allow running these in parallel when compiled with an executable using -threaded. See also my other comment on this line.
There's not much difference between sum and max when only one tool is installed as the other will fail near-instantly, and they take basically the same time when both are present.
The downside is that we do an extra version check we wouldn't do with the old lazy approach when all the following are true

  • -threaded is absent
  • both tools are installed
  • stack-only or cabal-only cradle

I do think we can again avoid unneeded calls by first looking at all the cradle types and seeing what we will actually need to load instead of loading all unconditionally but my first priority was cutting the fragile mdo knot without incurring slowdowns so I'm doing baby steps.

That said, stack's version doesn't seem to be used anywhere so I'm not sure why we even read it.
Code compiles after simply removing the field definition and the getStackVersion call so it seems to have no effect besides in the exported API which a comment says is for test's sake.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deriving (Functor, Foldable, Traversable)

makeVersions :: LogAction IO (WithSeverity Log) -> FilePath -> IO BuildToolVersions
makeVersions l wdir = mapConcurrently (\v -> v l wdir) $ BuildToolVersions getCabalVersion getStackVersion
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These both take around 40ms -+ 10ms for me, so it doesn't seem that either has to wait much for the other, which would slow down stack only or cabal only scenarios.
Theoretically it speeds up the case where both are needed.
That said, stackVersion doesn't seem to be used anywhere?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is safe to just remove it since it is not even used. Easy to add them back if we need them in the future

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like it should be re-used by HLS instead of having separate impls.
Maybe it was at some point?

, getGhcVersion
, isCabalMultipleCompSupported
, ProgramVersions
, BuildToolVersions
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Breaking change. Can't seem to avoid it while cutting the knot - at most the old name can be kept.


resolveCradleAction :: Show a => LogAction IO (WithSeverity Log) -> (b -> CradleAction a) -> ResolvedCradles b -> FilePath -> ResolvedCradle b -> CradleAction a
resolveCradleAction l buildCustomCradle cs root cradle = addLoadStyleLogToCradleAction $
resolveCradleAction :: Show a => LogAction IO (WithSeverity Log) -> (b -> CradleAction a) -> ResolvedCradles b -> FilePath -> ResolvedCradle b -> IO (CradleAction a)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this one can be made pure again, but it needs some other changes first

Comment on lines +543 to +597
cabalPathOutput = case res of
CradleSuccess out -> out
_ -> Nothing
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty awkward. Maybe callCabalPathForCompilerPath should be IO instead of CradleResultT ?

deriving (Functor, Foldable, Traversable)

makeVersions :: LogAction IO (WithSeverity Log) -> FilePath -> IO BuildToolVersions
makeVersions l wdir = mapConcurrently (\v -> v l wdir) $ BuildToolVersions getCabalVersion getStackVersion
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Welp, forgot to click submit on my own review back when opening the PR.

why do we use mapConcurrently here?

To allow running these in parallel when compiled with an executable using -threaded. See also my other comment on this line.
There's not much difference between sum and max when only one tool is installed as the other will fail near-instantly, and they take basically the same time when both are present.
The downside is that we do an extra version check we wouldn't do with the old lazy approach when all the following are true

  • -threaded is absent
  • both tools are installed
  • stack-only or cabal-only cradle

I do think we can again avoid unneeded calls by first looking at all the cradle types and seeing what we will actually need to load instead of loading all unconditionally but my first priority was cutting the fragile mdo knot without incurring slowdowns so I'm doing baby steps.

That said, stack's version doesn't seem to be used anywhere so I'm not sure why we even read it.
Code compiles after simply removing the field definition and the getStackVersion call so it seems to have no effect besides in the exported API which a comment says is for test's sake.

deriving (Functor, Foldable, Traversable)

makeVersions :: LogAction IO (WithSeverity Log) -> FilePath -> IO BuildToolVersions
makeVersions l wdir = mapConcurrently (\v -> v l wdir) $ BuildToolVersions getCabalVersion getStackVersion
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alexfmpe alexfmpe force-pushed the cabal-path-correctness branch from a7a9fed to cc3ac98 Compare June 4, 2025 14:47
@alexfmpe alexfmpe force-pushed the cabal-path-correctness branch from cc3ac98 to 3025d2d Compare June 4, 2025 15:28
-- checking its version
| LoadWithContext _ <- loadStyle ->
if ghc >= makeVersion [9,4]
if isCabalMultipleCompSupported Nothing (Just ghc)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can just do pure $ bool LoadFile loadStyle $ isCabalMultipleCompSupported Nothing ghc_version for the whole thing if we don't care about the specific log below.
Alternatively, if we always want it on every check, we can also have a util that does that consistently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants