1-- SPDX-FileCopyrightText: 2026 Sören Tempel <soeren+git@soeren-tempel.net>2--3-- SPDX-License-Identifier: GPL-3.0-only45module Linker (linkerTests) where67import Control.Exception (throwIO)8import Data.List (find)9import Data.Maybe (mapMaybe)10import Language.QBE11 ( Definition (DefData, DefType),12 ExecError (EUnknownEntry),13 Program,14 globalFuncs,15 localDefs,16 parse,17 typeDefs,18 )19import Language.QBE.Linker (link)20import Language.QBE.Types qualified as QBE21import System.FilePath ((</>))22import Test.Tasty23import Test.Tasty.HUnit24import Util (parseArchive)2526parseFile :: FilePath -> IO Program27parseFile fileName = do28 content <- readFile $ "test" </> "testdata" </> "linker" </> fileName29 case parse fileName content of30 Right prog -> pure prog31 Left err -> throwIO err3233findData :: Program -> [QBE.DataDef]34findData = mapMaybe getData35 where36 getData :: Definition -> Maybe QBE.DataDef37 getData (DefData dataDef) = Just dataDef38 getData _ = Nothing3940findFunc :: Program -> String -> IO QBE.FuncDef41findFunc prog name = do42 let funcs = globalFuncs prog43 entryIdent = QBE.GlobalIdent name44 case find (\f -> QBE.fName f == entryIdent) funcs of45 Just x -> pure x46 Nothing -> throwIO $ EUnknownEntry entryIdent4748findType :: Program -> String -> IO QBE.TypeDef49findType prog name = do50 let toFind = QBE.UserIdent name51 case find (isType toFind) prog of52 Just (DefType ty) -> pure ty53 _ -> fail $ "unknown type '" ++ show toFind ++ "'"54 where55 isType toFind (DefType ty) = QBE.aggName ty == toFind56 isType _ _ = False5758------------------------------------------------------------------------5960linkerTests :: TestTree61linkerTests =62 testGroup63 "Test the QBE Linker"64 [ testCase "Link file with same data definition" $65 do66 prog <- parseFile "data-definition-and-call.qbe"67 let linked = link [prog, prog]6869 map QBE.name (findData linked)70 @?= [QBE.GlobalIdent ".L116", QBE.GlobalIdent ".L116.QG1"]7172 func <- findFunc (reverse linked) "__assert"73 let block = QBE.fEntry func74 funcVal = QBE.VConst (QBE.Const $ QBE.Global (QBE.GlobalIdent "error"))75 funcArg =76 QBE.ArgReg77 (QBE.ABase QBE.Long)78 (QBE.VConst (QBE.Const $ QBE.Global (QBE.GlobalIdent ".L116.QG1")))79 QBE.stmt block @?= [QBE.Call Nothing funcVal [funcArg]],80 testCase "Link files with helper function" $81 do82 progs <- parseArchive "linker/same-helper-function.ar"83 let linked = link progs8485 localDefs linked86 @?= [QBE.GlobalIdent "myhelper", QBE.GlobalIdent "myhelper.QG1"]8788 func <- findFunc linked "myfunc2"89 let block = QBE.fEntry func90 funcVal = QBE.VConst (QBE.Extern $ QBE.GlobalIdent "callback")91 funcArg =92 QBE.ArgReg93 (QBE.ABase QBE.Long)94 (QBE.VConst (QBE.Extern $ QBE.GlobalIdent "myhelper.QG1"))95 QBE.stmt block @?= [QBE.Call Nothing funcVal [funcArg]],96 testCase "Data reference within a data definition" $97 do98 prog <- parseFile "data-reference-in-data-definition.qbe"99 let linked = link [prog, prog]100101 -- TODO: actually check the data mamber102 localDefs linked103 @?= [ QBE.GlobalIdent "somedata",104 QBE.GlobalIdent "moredata",105 QBE.GlobalIdent "somedata.QG1",106 QBE.GlobalIdent "moredata.QG1"107 ],108 testCase "Link files with conflicting type definition" $109 do110 progs <- parseArchive "linker/same-type-in-function-return.ar"111 let linked = link progs112113 typeDefs linked114 @?= [QBE.UserIdent "retTy", QBE.UserIdent "retTy.QU1"]115116 func <- findFunc linked "idivmax"117 QBE.fAbity func @?= Just (QBE.AUserDef $ QBE.UserIdent "retTy.QU1"),118 testCase "Type definition with fields" $119 do120 prog <- parseFile "type-definition-with-fields.ar"121 let linked = link [prog, prog]122123 typeDefs linked124 @?= [ QBE.UserIdent "type.39",125 QBE.UserIdent "type.38",126 QBE.UserIdent "something",127 QBE.UserIdent "type.39.QU1",128 QBE.UserIdent "type.38.QU1",129 QBE.UserIdent "something.QU1"130 ]131132 ty1 <- findType linked "something.QU1"133 ty1134 @?= QBE.TypeDef135 (QBE.UserIdent "something.QU1")136 Nothing137 (QBE.AUnion [[(QBE.SUserDef $ QBE.UserIdent "type.38.QU1", Just 1)]])138139 ty2 <- findType linked "type.38.QU1"140 ty2141 @?= QBE.TypeDef142 (QBE.UserIdent "type.38.QU1")143 (Just 8)144 ( QBE.ARegular145 [ (QBE.SExtType $ QBE.Base QBE.Long, Just 1),146 (QBE.SUserDef $ QBE.UserIdent "type.39.QU1", Just 1)147 ]148 )149 ]