David Eichmann pushed to branch wip/davide/ghc-toolchain-llvm-versions at Glasgow Haskell Compiler / GHC Commits: 6278067c by David Eichmann at 2026-05-13T11:23:03+01:00 ghc-toolchain: implement llvm program versioning logic - - - - - 3 changed files: - m4/ghc_toolchain.m4 - utils/ghc-toolchain/exe/Main.hs - utils/ghc-toolchain/src/GHC/Toolchain/Program.hs Changes: ===================================== m4/ghc_toolchain.m4 ===================================== @@ -55,6 +55,8 @@ AC_DEFUN([FIND_GHC_TOOLCHAIN], rm -f acargs echo "--triple=$HostPlatform" >> acargs echo "--output=$1/default.host.target.ghc-toolchain" >> acargs + echo "--llvm-min-version=$LlvmMinVersion" >> acargs + echo "--llvm-max-version-excl=$LlvmMaxVersion" >> acargs echo "--cc=$CC_STAGE0" >> acargs echo "--cc-link=$CC_STAGE0" >> acargs echo "--ar=$AR_STAGE0" >> acargs @@ -82,6 +84,8 @@ AC_DEFUN([FIND_GHC_TOOLCHAIN], echo "--triple=$target" >> acargs echo "--output=$1/default.target.ghc-toolchain" >> acargs echo "--llvm-triple=$LlvmTarget" >> acargs + echo "--llvm-min-version=$LlvmMinVersion" >> acargs + echo "--llvm-max-version-excl=$LlvmMaxVersion" >> acargs echo "--cc=$CC" >> acargs echo "--cxx=$CXX" >> acargs echo "--cpp=$CPPCmd" >> acargs ===================================== utils/ghc-toolchain/exe/Main.hs ===================================== @@ -39,6 +39,8 @@ data Opts = Opts , optTargetPrefix :: Maybe String , optLocallyExecutable :: Maybe Bool , optLlvmTriple :: Maybe String + , optLlvmMinVersion :: Maybe Int -- ^ Minimum supported LLVM version (inclusive) + , optLlvmMaxVersion :: Maybe Int -- ^ Maximum supported LLVM version (inclusive) , optOutput :: Maybe String , optCc :: ProgOpt , optCxx :: ProgOpt @@ -98,6 +100,8 @@ emptyOpts = Opts , optTargetPrefix = Nothing , optLocallyExecutable = Nothing , optLlvmTriple = Nothing + , optLlvmMinVersion = Nothing + , optLlvmMaxVersion = Nothing , optOutput = Nothing , optCc = po0 , optCxx = po0 @@ -163,6 +167,10 @@ _optTriple = Lens optTriple (\x o -> o {optTriple=x}) _optLlvmTriple :: Lens Opts (Maybe String) _optLlvmTriple = Lens optLlvmTriple (\x o -> o {optLlvmTriple=x}) +_optLlvmMinVersion, _optLlvmMaxVersion :: Lens Opts (Maybe Int) +_optLlvmMinVersion = Lens optLlvmMinVersion (\x o -> o {optLlvmMinVersion=x}) +_optLlvmMaxVersion = Lens optLlvmMaxVersion (\x o -> o {optLlvmMaxVersion=x}) + _optOutput :: Lens Opts (Maybe String) _optOutput = Lens optOutput (\x o -> o {optOutput=x}) @@ -192,6 +200,8 @@ options = [ tripleOpt , targetPrefixOpt , llvmTripleOpt + , llvmMinVersionOpt + , llvmMaxVersionOpt , verbosityOpt , keepTempOpt , outputOpt @@ -259,6 +269,9 @@ options = tripleOpt = Option ['t'] ["triple"] (ReqArg (set _optTriple . Just) "TRIPLE") "Target triple" llvmTripleOpt = Option [] ["llvm-triple"] (ReqArg (set _optLlvmTriple . Just) "LLVM-TRIPLE") "LLVM Target triple" + llvmMinVersionOpt = Option [] ["llvm-min-version"] (ReqArg (set _optLlvmMinVersion . Just . read) "LLVM-MIN-VERSION") "LLVM min version (inclusive)" + llvmMaxVersionOpt = Option [] ["llvm-max-version-excl"] (ReqArg (set _optLlvmMaxVersion . Just . subtract 1 . read) "LLVM-MAX-VERSION") "LLVM max version (exclusive)" + targetPrefixOpt = Option ['T'] ["target-prefix"] (ReqArg (set _optTargetPrefix . Just) "PREFIX") "A target prefix which will be added to all tool names when searching for toolchain components" @@ -289,13 +302,11 @@ formatOpts = [ validateOpts :: Opts -> [String] validateOpts opts = mconcat - [ assertJust _optTriple "missing --triple flag" - , assertJust _optOutput "missing --output flag" + [ ["missing --triple flag" | isNothing (optTriple opts)] + , ["missing --output flag" | isNothing (optOutput opts)] + , ["missing --llvm-min-version flag" | isNothing (optLlvmMinVersion opts) ] + , ["missing --llvm-max-version-excl flag" | isNothing (optLlvmMinVersion opts) ] ] - where - assertJust :: Lens Opts (Maybe a) -> String -> [String] - assertJust lens msg = - [ msg | Nothing <- pure $ view lens opts ] main :: IO () main = do @@ -448,6 +459,8 @@ mkTarget opts = do normalised_triple <- normaliseTriple (fromMaybe (error "missing --triple") (optTriple opts)) -- Use Llvm target if specified, otherwise use triple as llvm target let tgtLlvmTarget = fromMaybe normalised_triple (optLlvmTriple opts) + let llvmMinVersion = fromMaybe (error "missing --llvm-min-version") (optLlvmMinVersion opts) + let llvmMaxVersion = fromMaybe (error "missing --llvm-max-version-excl") (optLlvmMaxVersion opts) (archOs, tgtVendor) <- do cc0 <- findBasicCc (optCc opts) @@ -480,13 +493,14 @@ mkTarget opts = do throwE "Neither a object-merging tool (e.g. ld -r) nor an ar that supports -L is available" -- LLVM toolchain - llc <- optional $ findProgram "llc" (optLlc opts) ["llc"] - opt <- optional $ findProgram "opt" (optOpt opts) ["opt"] - llvmAs <- optional $ findProgram "llvm assembler" (optLlvmAs opts) ["clang"] + let findLlvmProgram' = findLlvmProgram llvmMinVersion llvmMaxVersion + llc <- optional $ findLlvmProgram' "llc" (optLlc opts) "llc" True + opt <- optional $ findLlvmProgram' "opt" (optOpt opts) "opt" True + llvmAs <- optional $ findLlvmProgram' "llvm assembler" (optLlvmAs opts) "clang" True -- for windows, also used for cross compiling windres <- optional $ findProgram "windres" (optWindres opts) ["windres"] - dlltool <- optional $ findProgram "dlltool" (optDlltool opts) ["llvm-dlltool"] + dlltool <- optional $ findLlvmProgram' "dlltool" (optDlltool opts) "llvm-dlltool" False -- Darwin-specific utilities (otool, installNameTool) <- ===================================== utils/ghc-toolchain/src/GHC/Toolchain/Program.hs ===================================== @@ -16,6 +16,7 @@ module GHC.Toolchain.Program , _poPath , _poFlags , findProgram + , findLlvmProgram -- * Compiler programs , compile , supportsTarget @@ -23,7 +24,8 @@ module GHC.Toolchain.Program import Control.Monad import Control.Monad.IO.Class -import Data.List (intercalate, isPrefixOf) +import Data.Char (isDigit) +import Data.List (find, intercalate, isPrefixOf, tails) import Data.Maybe import System.FilePath import System.Directory @@ -131,17 +133,13 @@ programFromOpt userSpec path flags = Program { prgPath = fromMaybe path (poPath -- in the given list of candidates. -- -- If the 'ProgOpt' program flags are unspecified the program will have an empty list of flags. -findProgram :: String +findProgram :: String -- ^ The program description -> ProgOpt -- ^ path provided by user -> [FilePath] -- ^ candidate names -> M Program findProgram description userSpec candidates - | Just path <- poPath userSpec = do - let err = - [ "Failed to find " ++ description ++ "." - , "Looked for user-specified program '" ++ path ++ "' in the system search path." - ] - toProgram <$> find_it path <|> throwEs err + | Just findProgramFromProgOpts <- maybeFindProgramFromProgOpts description userSpec + = findProgramFromProgOpts | otherwise = do env <- getEnv @@ -154,17 +152,78 @@ findProgram description userSpec candidates [ "Failed to find " ++ description ++ "." , "Looked for one of " ++ show candidates' ++ " in the system search path." ] - toProgram <$> oneOf' err (map find_it candidates') <|> throwEs err + mkProgram userSpec <$> oneOf' err (map findExecutableErr candidates') <|> throwEs err + +-- | Tries to find an llvm program with the highest supported llvm versions. +-- This searches for an explicitly versioned executable (postfixed with the llvm version). +-- If an explicitly versioned executable is not found, then this searches for a non-explicitly +-- versioned executable. If supported, the llvm version is checked by passing @--version@ to +-- the executable. +-- +-- If the 'ProgOpt' program flags are unspecified the program will have an empty list of flags. +findLlvmProgram :: Int -- ^ Min llvm version (inclusive) + -> Int -- ^ Max llvm version (inclusive) + -> String -- ^ The llvm program description + -> ProgOpt -- ^ path provided by user + -> FilePath -- ^ Candidate name + -> Bool -- ^ True if the program supports the @--version@ flag and the output + -- contains the llvm version number in the form @version <LLVM_VERSION>@ + -> M Program +findLlvmProgram minLlvmVersion maxLlvmVersion description userSpec candidate checkVersion + | Just findProgramFromProgOpts <- maybeFindProgramFromProgOpts description userSpec + = findProgramFromProgOpts + + | otherwise = do + program <- findProgram description userSpec (versionedCandidates ++ [candidate]) + when checkVersion $ do + -- Extract the version from the `--version` output + versionOutput <- readProgramStdout program ["--version"] + let versionStrPrefix = "version " + + versionMay :: Maybe Int + versionMay = fmap (read . takeWhile isDigit . drop (length versionStrPrefix)) + . find (versionStrPrefix `isPrefixOf`) + $ tails versionOutput + + errSupportedVersions = prgPath program <> ": We only support llvm " <> show minLlvmVersion <> " upto " <> show maxLlvmVersion <> " (non-inclusive)" + case versionMay of + Nothing -> throwE (errSupportedVersions <> " (no version found).") + Just version -> when + (version < minLlvmVersion || version > maxLlvmVersion) + (throwE $ errSupportedVersions <> " (found " <> show version <> ").") + return program where - toProgram path = Program { prgPath = path, prgFlags = fromMaybe [] (poFlags userSpec) } - - find_it name = do - r <- liftIO $ findExecutable name - case r of - Nothing -> throwE $ name ++ " not found in search path" - -- Use the given `prgPath` or candidate name rather than the - -- absolute path returned by `findExecutable`. - Just _x -> return name + versionedCandidates = + [ candidate <> postfix + | llvmVersion <- show <$> [minLlvmVersion, minLlvmVersion-1 .. maxLlvmVersion] + , postfix <- + [ "-" <> llvmVersion + , "-" <> llvmVersion <> ".0" + , llvmVersion + ] + ] + +maybeFindProgramFromProgOpts :: String -> ProgOpt -> Maybe (M Program) +maybeFindProgramFromProgOpts description userSpec = case poPath userSpec of + Nothing -> Nothing + Just path -> do + let err = + [ "Failed to find " ++ description ++ "." + , "Looked for user-specified program '" ++ path ++ "' in the system search path." + ] + Just (mkProgram userSpec <$> findExecutableErr path <|> throwEs err) + +mkProgram :: ProgOpt -> FilePath -> Program +mkProgram userSpec path = Program { prgPath = path, prgFlags = fromMaybe [] (poFlags userSpec) } + +findExecutableErr :: String -> M FilePath +findExecutableErr name = do + r <- liftIO $ findExecutable name + case r of + Nothing -> throwE $ name ++ " not found in search path" + -- Use the given `prgPath` or candidate name rather than the + -- absolute path returned by `findExecutable`. + Just _x -> return name -------------------- Compiling utilities -------------------- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/6278067c48bac1668f26bb8cc6b5f81e... -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/6278067c48bac1668f26bb8cc6b5f81e... You're receiving this email because of your account on gitlab.haskell.org.