1-- SPDX-FileCopyrightText: 2026 Sören Tempel <soeren+git@soeren-tempel.net>2--3-- SPDX-License-Identifier: GPL-3.0-only45-- | This module implements a "linker" for QBE source files. It takes multiple6-- 'Language.QBE.Program' values and merges them by resolving conflicting7-- non-exporting definitions by assising them a new (unique) name.8module Language.QBE.Linker (link) where910import Control.Monad (foldM)11import Control.Monad.State (State, evalState, get, gets, modify)12import Data.Map (Map)13import Data.Map qualified as Map14import Language.QBE (Definition (DefData, DefFile, DefFunc, DefType), Program, localDefs, typeDefs)15import Language.QBE.Types qualified as QBE16import Language.QBE.Uses qualified as USE1718data Env19 = Env20 { envGlobals :: Map QBE.GlobalIdent Int,21 envTypes :: Map QBE.UserIdent Int22 }2324mkEnv :: Env25mkEnv = Env Map.empty Map.empty2627------------------------------------------------------------------------2829-- TODO: Some sort of type class for Idents?30rename :: (Ord a) => (a -> Int -> a) -> Map a Int -> a -> a31rename inc varOcc i = maybe i (inc' i) $ Map.lookup i varOcc32 where33 inc' ident n = if n >= 1 then inc ident n else ident3435renameGlobal :: Map QBE.GlobalIdent Int -> QBE.GlobalIdent -> QBE.GlobalIdent36renameGlobal = rename (\(QBE.GlobalIdent s) n -> QBE.GlobalIdent $ s ++ ".QG" ++ show n)3738renameType :: Map QBE.UserIdent Int -> QBE.UserIdent -> QBE.UserIdent39renameType = rename (\(QBE.UserIdent s) n -> QBE.UserIdent $ s ++ ".QU" ++ show n)4041renameDef :: Env -> Definition -> Definition42renameDef Env {envGlobals = varOcc, envTypes = tyOcc} (DefFunc funcDef) =43 DefFunc $ USE.mapGlobals (USE.mapType funcDef (renameType tyOcc)) (renameGlobal varOcc)44renameDef Env {envGlobals = varOcc} (DefData dataDef) =45 DefData $ USE.mapGlobals dataDef (renameGlobal varOcc)46renameDef Env {envTypes = tyOcc} (DefType typeDef) =47 DefType $ USE.mapType typeDef (renameType tyOcc)48renameDef _ def@(DefFile _) = def4950renameDefs :: Env -> Program -> Program51renameDefs env = map (renameDef env)5253renameProg :: Program -> State Env Program54renameProg prog = gets (`renameDefs` prog)5556------------------------------------------------------------------------5758count :: (Ord a) => Map a Int -> [a] -> Map a Int59count knownVars defs =60 let inc = maybe 0 (+ 1)61 in foldl (flip $ upsert inc) knownVars defs62 where63 -- TODO: Remove this once we upgrade to containers >= 0.8.1.64 -- See: <https://github.com/haskell/containers/issues/809>.65 upsert f = Map.alter (Just . f)6667uniqProg :: Program -> State Env Program68uniqProg prog = do69 Env {envGlobals = knownVars, envTypes = knownTypes} <- get70 let varOcc = count knownVars (localDefs prog)71 tyOcc = count knownTypes (typeDefs prog)72 modify (\e -> e {envGlobals = varOcc, envTypes = tyOcc})73 renameProg prog7475-- | Merge multiple QBE sources files into one, renaming non-exported definitions.76link :: [Program] -> Program77link progs = evalState (foldM go [] progs) mkEnv78 where79 go acc x = (acc ++) <$> uniqProg x