{-# LANGUAGE DataKinds #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} {-| Module : Language.QBE Description : Types and Pretty instances for the QBE IL Copyright : (c) Francesco Gazzetta, 2022 License : BSD-3-Clause Maintainer : Francesco Gazzetta This module contains datatypes representing the various structures of the [intermediate language](https://c9x.me/compile/doc/il.html) of the [QBE](https://c9x.me/compile/) compiler backend. All datatypes also have 'Pretty' instances from the [@prettyprinter@](https://hackage.haskell.org/package/prettyprinter) library. You can render QBE IL source files, or any part of them, with something like: > render :: Pretty a => a -> Text > render = renderStrict . layoutPretty defaultLayoutOptions . pretty >>> render $ Ret $ Just $ ValTemporary "a" "ret %a" >>> Text.putStrLn $ render $ Program [] [] [FuncDef [] Nothing "main" … function w $main () { @start ⋮ -} module Language.QBE ( -- * Identifiers RawIdent , Sigil(..) , Ident(..) -- * Types , BaseTy(..) , ExtTy(..) -- * Constants , Const(..) -- * Linkage , Linkage(..) -- * Definitions , Alignment , Size , Amount -- ** Aggregate types , TypeDef(..) , SubTy(..) -- ** Data , DataDef(..) , DataItem(..) , Field(..) -- ** Functions , FuncDef(..) , AbiTy(..) , Param(..) , Variadic(..) , prettyVariadic -- * Control , Val(..) , Block(..) , Jump(..) -- * Instructions , Phi(..) , PhiArg(..) , Inst(..) , Assignment(..) , pattern (:=) , IntRepr(..) , BinaryOp(..) , Comparison(..) , Arg(..) -- * Program , Program(..) ) where import Data.Text (Text) import Data.Text.Short (ShortText) import qualified Data.Text.Short as TS import Data.ByteString (ByteString) import Data.Word (Word64) import Data.List.NonEmpty (NonEmpty, toList) import Data.Maybe (maybeToList) import Prettyprinter ( Pretty(pretty), Doc, (<+>), vsep, hsep, hang, punctuate, group, flatAlt , space, encloseSep, tupled, comma, equals, braces, lbrace, rbrace ) -- Instances import Data.Hashable (Hashable) import Control.DeepSeq (NFData) import Data.String (IsString) -- * Identifiers ---------------- -- | A raw identifier string, with no sigil information attached type RawIdent = ShortText -- | Sigils are used to differentiate the verious types of 'Ident'ifier. data Sigil = AggregateTy -- ^ @:@ | Global -- ^ @$@ | Temporary -- ^ @%@ | Label -- ^ @\@@ deriving (Show, Eq) -- | QBE identifiers. The sigil is represented at the type level, so that -- mixing incompatible identifiers is impossible. -- -- >>> :set -XOverloadedStrings -- >>> :set -XDataKinds -- >>> :set -XTypeApplications -- >>> pretty $ Jmp $ Ident @'Label "a" -- jmp @a -- >>> pretty $ Jmp $ Ident @'Global "a" -- :5:16: error: -- • Couldn't match type ‘'Global’ with ‘'Label’ -- Expected: Ident 'Label -- Actual: Ident 'Global -- • In the second argument of ‘($)’, namely ‘Ident @'Global "a"’ -- In the second argument of ‘($)’, namely ‘Jmp $ Ident @'Global "a"’ -- In the expression: pretty $ Jmp $ Ident @'Global "a" newtype Ident (t :: Sigil) = Ident RawIdent deriving (Show, Eq, Ord, IsString, NFData, Hashable) instance Pretty (Ident 'AggregateTy) where pretty (Ident raw) = pretty ':' <> pretty (TS.toText raw) instance Pretty (Ident 'Global) where pretty (Ident raw) = pretty '$' <> pretty (TS.toText raw) instance Pretty (Ident 'Temporary) where pretty (Ident raw) = pretty '%' <> pretty (TS.toText raw) instance Pretty (Ident 'Label) where pretty (Ident raw) = pretty '@' <> pretty (TS.toText raw) -- * Types ---------- -- | Base types data BaseTy = Word -- ^ @w@ | Long -- ^ @l@ | Single -- ^ @s@ | Double -- ^ @d@ deriving (Show, Eq) instance Pretty BaseTy where pretty Word = pretty 'w' pretty Long = pretty 'l' pretty Single = pretty 's' pretty Double = pretty 'd' -- | Extended types data ExtTy = BaseTy BaseTy | Byte -- ^ @b@ | HalfWord -- ^ @h@ deriving (Show, Eq) instance Pretty ExtTy where pretty (BaseTy baseTy) = pretty baseTy pretty Byte = pretty 'b' pretty HalfWord = pretty 'h' -- * Constants -------------- -- | Constant/immediate data Const -- MAYBE just use a signed type = CInt Bool Word64 -- ^ 64 bit integer. The 'Bool' is whether to negate. | CSingle Float -- ^ Single-precision float | CDouble Double -- ^ Double-precision float | CGlobal (Ident 'Global) -- ^ Global symbol deriving (Show, Eq) instance Pretty Const where pretty (CInt negative int) | negative = pretty '-' <> pretty int | otherwise = pretty int pretty (CSingle float) = "s_" <> pretty float pretty (CDouble double) = "d_" <> pretty double pretty (CGlobal ident) = pretty ident -- * Linkage ------------ data Linkage = Export -- ^ Marks the defined item as visible outside the current file's scope | Section ShortText (Maybe Text) -- ^ Section name, with optional linker flags deriving (Show, Eq) instance Pretty Linkage where pretty Export = "export" pretty (Section secName Nothing) = "section" <+> pretty (TS.toText secName) pretty (Section secName (Just secFlags)) = "section" <+> pretty (TS.toText secName) <+> pretty secFlags -- * Definitions ---------------- type Alignment = Word64 type Size = Word64 type Amount = Word64 -- ** Aggregate types --------------------- -- | Aggregate type data TypeDef = TypeDef (Ident 'AggregateTy) (Maybe Alignment) [(SubTy, Maybe Amount)] | Opaque (Ident 'AggregateTy) Alignment Size deriving (Show, Eq) instance Pretty TypeDef where pretty (TypeDef ident alignment def) = "type" <+> pretty ident <+> equals <> maybe mempty (\x -> space <> pretty x) alignment <+> braced (prettyItem <$> def) where prettyItem (subTy, Nothing ) = pretty subTy prettyItem (subTy, Just amount) = pretty subTy <+> pretty amount pretty (Opaque ident alignment size) = "type" <+> pretty ident <+> equals <+> "align" <+> pretty alignment <+> braces (pretty size) -- | A type that can be part of an aggregate type data SubTy = SubExtTy ExtTy | SubAggregateTy (Ident 'AggregateTy) deriving (Show, Eq) instance Pretty SubTy where pretty (SubExtTy extTy) = pretty extTy pretty (SubAggregateTy ident) = pretty ident -- ** Data ---------- -- | Global object definition data DataDef = DataDef [Linkage] (Ident 'Global) (Maybe Alignment) [Field] deriving (Show, Eq) instance Pretty DataDef where pretty (DataDef linkage ident alignment fields) = vsep [ vsep $ pretty <$> linkage , hsep $ ("data" <+> pretty ident <+> equals) : maybeToList (("align" <+>) . pretty <$> alignment) , braced (pretty <$> fields) ] data DataItem = Symbol (Ident 'Global) (Maybe Alignment) | String ByteString | Const Const deriving (Show, Eq) instance Pretty DataItem where pretty (Symbol ident alignment) = hsep $ pretty ident : maybeToList ((pretty '+' <+>) . pretty <$> alignment) pretty (String bs) = pretty $ show bs -- HACK: hoping that the escape sequences are the same... pretty (Const c) = pretty c data Field = FieldExtTy ExtTy (NonEmpty DataItem) | FieldZero Size deriving (Show, Eq) instance Pretty Field where pretty (FieldExtTy extTy items) = pretty extTy <+> hsep (toList $ pretty <$> items) pretty (FieldZero size) = pretty 'z' <+> pretty size -- ** Functions --------------- -- TODO use record syntax on long types like this one -- | Function definition. The 'Maybe (Ident \'Temporary)' is the environment data FuncDef = FuncDef [Linkage] (Maybe AbiTy) (Ident 'Global) (Maybe (Ident 'Temporary)) [Param] Variadic (NonEmpty Block) deriving (Show, Eq) instance Pretty FuncDef where pretty (FuncDef linkage abiTy ident env params variadic blocks) = vsep [ vsep $ pretty <$> linkage , "function" <+> pretty abiTy <+> pretty ident <+> tupled ( maybeToList (("env" <+>) . pretty <$> env) ++ fmap pretty params ++ maybeToList (prettyVariadic variadic) ) <+> lbrace , vsep $ toList $ pretty <$> blocks , rbrace ] data AbiTy = AbiBaseTy BaseTy | AbiAggregateTy (Ident 'AggregateTy) deriving (Show, Eq) instance Pretty AbiTy where pretty (AbiBaseTy baseTy) = pretty baseTy pretty (AbiAggregateTy ident) = pretty ident -- | Function parameter data Param = Param AbiTy (Ident 'Temporary) deriving (Show, Eq) instance Pretty Param where pretty (Param abiTy ident) = pretty abiTy <+> pretty ident -- | Indicates the presence or absence of a variadic marker data Variadic = Variadic | NoVariadic deriving (Show, Eq) -- | 'Variadic' → @Just "..."@ -- 'NoVariadic' → @Nothing@ prettyVariadic :: Variadic -> Maybe (Doc a) prettyVariadic Variadic = Just "..." prettyVariadic NoVariadic = Nothing -- * Control ------------ -- | Value, either an immediate or a global or temporary identifier. data Val = ValConst Const | ValTemporary (Ident 'Temporary) | ValGlobal (Ident 'Global) deriving (Show, Eq) instance Pretty Val where pretty (ValConst c) = pretty c pretty (ValTemporary ident) = pretty ident pretty (ValGlobal ident) = pretty ident -- | Block of instructions beginning with a label and ending with a jump data Block = Block (Ident 'Label) [Phi] [Inst] Jump deriving (Show, Eq) instance Pretty Block where pretty (Block ident phis insts jump) = hang 4 $ vsep $ concat [ [pretty ident] , pretty <$> phis , pretty <$> insts , [pretty jump] ] -- | Jump instructions data Jump = Jmp (Ident 'Label) -- ^ Unconditional jump | Jnz Val (Ident 'Label) (Ident 'Label) -- ^ Conditional jump | Ret (Maybe Val) -- ^ Function return deriving (Show, Eq) instance Pretty Jump where pretty (Jmp ident) = "jmp" <+> pretty ident pretty (Jnz val label1 label2) = "jnz" <+> pretty val <> comma <+> pretty label1 <> comma <+> pretty label2 pretty (Ret val) = "ret" <+> pretty val -- * Instructions ----------------- -- MAYBE change [PhiArg] to Map (Ident 'Label) Val -- | Phi instruction data Phi = Phi Assignment [PhiArg] deriving (Show, Eq) instance Pretty Phi where pretty (Phi assignment args) = pretty assignment <+> "phi" <+> hsep (punctuate comma $ pretty <$> args) -- | Phi instruction argument, associating a 'Val' to a 'Label' data PhiArg = PhiArg (Ident 'Label) Val deriving (Show, Eq) instance Pretty PhiArg where pretty (PhiArg label val) = pretty label <+> pretty val -- | Instruction data Inst -- Arithmetic and Bits = BinaryOp Assignment BinaryOp Val Val -- ^ Binary arithmetic and bit operations | Neg Assignment Val -- ^ @neg@ -- Memory -- | @stored@/@stores@/@storel@/@storew@/@storeh@/@storeb@ | Store ExtTy Val Val -- MAYBE collapse all the Loads in a single Load constructor and just discard -- the intrepr when unused. -- | @loadw@/@loadl@/@loads@/@loadd@ | Load Assignment BaseTy Val -- | @loadsw@/@loaduw@ | LoadW Assignment IntRepr Val -- | @loadsh@/@loaduh@ | LoadH Assignment IntRepr Val -- | @loadsb@/@loadub@ | LoadB Assignment IntRepr Val -- Comparisons | Compare Assignment Comparison BaseTy Val Val -- Conversions -- | @extsw@/@extuw@ | ExtW Assignment IntRepr Val -- | @extsh@/@extuh@ | ExtH Assignment IntRepr Val -- | @extsb@/@extub@ | ExtB Assignment IntRepr Val -- | @exts@. There is only one possible instruction type, so there's -- only an 'Ident' instead of a full 'Assignment' | ExtS (Ident 'Temporary) Val -- | @truncd@. There is only one possible instruction type, so there's -- only an 'Ident' instead of a full 'Assignment' | TruncD (Ident 'Temporary) Val -- | @stosi@/@stoui@ | StoI Assignment IntRepr Val -- | @dtosi@/@dtoui@ | DtoI Assignment IntRepr Val -- | @swtof@/@uwtof@ | WtoF Assignment IntRepr Val -- | @sltof@/@ultof@ | LtoF Assignment IntRepr Val -- Cast and Copy -- | @cast@ | Cast Assignment Val -- | @copy@ | Copy Assignment Val -- Calls -- | @call@. The fields are: assignment, function name, environment, arguments, variadic arguments | Call (Maybe (Ident 'Temporary, AbiTy)) Val (Maybe Val) [Arg] [Arg] -- Variadic -- | @vastart@, initializes a variable argument list | VaStart (Ident 'Temporary) -- | @vaarg@, fetches the next argument from a variable argument list | VaArg Assignment (Ident 'Temporary) deriving (Show, Eq) instance Pretty Inst where pretty (BinaryOp assignment op v1 v2) = pretty assignment <+> pretty op <+> pretty v1 <> comma <+> pretty v2 pretty (Neg assignment v) = pretty assignment <+> "neg" <+> pretty v pretty (Store ty v address) = "store" <> pretty ty <+> pretty v <> comma <+> pretty address pretty (Load assignment loadTy addr) = pretty assignment <+> "load" <> pretty loadTy <+> pretty addr pretty (LoadW assignment intRepr addr) = pretty assignment <+> "load" <> pretty intRepr <> pretty 'w' <+> pretty addr pretty (LoadH assignment intRepr addr) = pretty assignment <+> "load" <> pretty intRepr <> pretty 'h' <+> pretty addr pretty (LoadB assignment intRepr addr) = pretty assignment <+> "load" <> pretty intRepr <> pretty 'b' <+> pretty addr pretty (Compare assignment comp compTy v1 v2) = pretty assignment <+> pretty 'c' <> pretty comp <> pretty compTy <+> pretty v1 <> comma <+> pretty v2 pretty (ExtW assignment intRepr v) = pretty assignment <+> "ext" <> pretty intRepr <> pretty 'w' <+> pretty v pretty (ExtH assignment intRepr v) = pretty assignment <+> "ext" <> pretty intRepr <> pretty 'h' <+> pretty v pretty (ExtB assignment intRepr v) = pretty assignment <+> "ext" <> pretty intRepr <> pretty 'b' <+> pretty v pretty (ExtS res v) = pretty res <+> equals <> pretty 'd' <+> "exts" <+> pretty v pretty (TruncD res v) = pretty res <+> equals <> pretty 's' <+> "truncd" <+> pretty v pretty (StoI assignment intRepr v) = pretty assignment <+> "sto" <> pretty intRepr <> pretty 'i' <+> pretty v pretty (DtoI assignment intRepr v) = pretty assignment <+> "dto" <> pretty intRepr <> pretty 'i' <+> pretty v pretty (WtoF assignment intRepr v) = pretty assignment <+> pretty intRepr <> "wtof" <+> pretty v pretty (LtoF assignment intRepr v) = pretty assignment <+> pretty intRepr <> "ltof" <+> pretty v pretty (Cast assignment v) = pretty assignment <+> "cast" <+> pretty v pretty (Copy assignment v) = pretty assignment <+> "copy" <+> pretty v pretty (Call assignment func env args variadics) = hsep $ maybeToList (prettyAssignment <$> assignment) ++ [ "call" , pretty func , tupled $ maybeToList (("env" <+>) . pretty <$> env) ++ fmap pretty args ++ variadics' ] where prettyAssignment (ident, ty) = pretty ident <+> equals <> pretty ty variadics' = if null variadics then [] else "..." : fmap pretty variadics pretty (VaStart argList) = "vastart" <+> pretty argList pretty (VaArg assignment argList) = pretty assignment <+> "vaarg" <+> pretty argList -- | Represents the @%x =t@ part of an instruction. data Assignment = Assignment (Ident 'Temporary) BaseTy deriving (Show, Eq) -- | Infix synonym of 'Assignment' pattern (:=) :: Ident 'Temporary -> BaseTy -> Assignment pattern (:=) ident ty = Assignment ident ty instance Pretty Assignment where pretty (Assignment ident ty) = pretty ident <+> equals <> pretty ty -- | Integer representation data IntRepr = Signed | Unsigned deriving (Show, Eq) -- | Binary arithmetic and bit operations data BinaryOp -- | @add@ = Add -- | @sub@ | Sub -- | @div@/@udiv@. @Div Signed@ gets translated to @div@, so it will work -- also on floats | Div IntRepr -- | @mul@ | Mul -- | @rem@/@urem@ | Rem IntRepr -- | @or@ | Or -- | @xor@ | Xor -- | @and@ | And -- | @sar@ | Sar -- | @shr@ | Shr -- | @shl@ | Shl deriving (Show, Eq) instance Pretty BinaryOp where pretty Add = "add" pretty Sub = "sub" pretty (Div Signed) = "div" pretty (Div Unsigned) = "udiv" pretty Mul = "mul" pretty (Rem Signed) = "rem" pretty (Rem Unsigned) = "rem" pretty Or = "or" pretty Xor = "xor" pretty And = "and" pretty Sar = "sar" pretty Shr = "shr" pretty Shl = "shl" -- | Comparison operators. -- Where there's a @'Maybe' 'IntRepr'@, 'Nothing' means floating point -- (@le@, @lt@, @ge@, @gt@), while @'Just' r@ means integer -- (@sle@, @ule@, @slt@, @ult@...) data Comparison -- Universal comparison = Eq -- ^ equality | Ne -- ^ inequality | Le (Maybe IntRepr) -- ^ lower or equal | Lt (Maybe IntRepr) -- ^ lower | Ge (Maybe IntRepr) -- ^ greater or equal | Gt (Maybe IntRepr) -- ^ greater -- Floating point only comparison | O -- ^ ordered (no operand is a NaN) (floating point only) | Uo -- ^ unordered (at least one operand is a NaN) (floating point only) deriving (Show, Eq) instance Pretty Comparison where pretty Eq = "eq" pretty Ne = "ne" pretty (Le intRepr) = pretty intRepr <> "le" pretty (Lt intRepr) = pretty intRepr <> "lt" pretty (Ge intRepr) = pretty intRepr <> "ge" pretty (Gt intRepr) = pretty intRepr <> "gt" pretty O = "o" pretty Uo = "uo" instance Pretty IntRepr where pretty Signed = pretty 's' pretty Unsigned = pretty 'u' -- | Function argument data Arg = Arg AbiTy Val deriving (Show, Eq) instance Pretty Arg where pretty (Arg abiTy val) = pretty abiTy <+> pretty val -- * Program ------------ -- | Datatypre representing a QBE IL source file data Program = Program [TypeDef] [DataDef] [FuncDef] deriving (Show, Eq) instance Pretty Program where pretty (Program typeDefs dataDefs funcDefs) = vsep $ concat [ pretty <$> typeDefs , pretty <$> dataDefs , pretty <$> funcDefs ] -- * Utilities -------------- -- | Like 'list' and 'tupled', but with braces braced :: [Doc ann] -> Doc ann braced = group . encloseSep (flatAlt "{ " "{") (flatAlt " }" "}") ", "