summaryrefslogtreecommitdiff
path: root/src/Language/Fiddle/Compiler/Backend
diff options
context:
space:
mode:
Diffstat (limited to 'src/Language/Fiddle/Compiler/Backend')
-rw-r--r--src/Language/Fiddle/Compiler/Backend/C.hs423
1 files changed, 340 insertions, 83 deletions
diff --git a/src/Language/Fiddle/Compiler/Backend/C.hs b/src/Language/Fiddle/Compiler/Backend/C.hs
index 645fa85..9dbbec6 100644
--- a/src/Language/Fiddle/Compiler/Backend/C.hs
+++ b/src/Language/Fiddle/Compiler/Backend/C.hs
@@ -1,27 +1,36 @@
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE TypeApplications #-}
module Language.Fiddle.Compiler.Backend.C (cBackend) where
-import Control.Arrow (Arrow (second))
+import Control.Arrow
+import Control.Monad (unless)
import Control.Monad.RWS
+import Control.Monad.State (State)
+import Control.Monad.Trans.Writer (Writer, execWriter)
import Data.Char (isSpace)
import Data.Data (Typeable, cast)
import Data.Foldable (forM_, toList)
import Data.Kind (Type)
+import qualified Data.List.NonEmpty as NonEmpty
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe (fromMaybe, mapMaybe)
+import Data.Set (Set)
+import qualified Data.Set as Set
import Data.String (IsString)
import Data.Text (Text)
import qualified Data.Text as Text
import Language.Fiddle.Ast
import Language.Fiddle.Compiler.Backend
import Language.Fiddle.Internal.UnitInterface
+import Language.Fiddle.Internal.UnitNumbers
import Language.Fiddle.Types
import Options.Applicative
+import Text.Printf (printf)
data ImplementationInHeader = ImplementationInHeader
@@ -30,17 +39,31 @@ data CBackendFlags = CBackendFlags
cHeaderOut :: FilePath
}
-newtype FilePosition = FilePosition Int
+type StructName = Text
+
+newtype Fragment = Fragment Int
deriving (Eq, Ord)
-headerPos :: FilePosition
-headerPos = FilePosition 0
+-- | Header fragment. The top. Starts which include guards and has include
+-- statements.
+hF :: Fragment
+hF = Fragment 0
+
+-- | Structures fragment. The text fragment where structures are defined.
+sF :: Fragment
+sF = Fragment 25
+
+-- | Implementation fragment. This is where function implementations go.
+iF :: Fragment
+iF = Fragment 75
-middlePos :: FilePosition
-middlePos = FilePosition 50
+-- | Assert fragment. This is where static asserts go.
+aF :: Fragment
+aF = Fragment 50
-footerPos :: FilePosition
-footerPos = FilePosition 100
+-- | Footer fragment. This is wehre the file include endif goes.
+fF :: Fragment
+fF = Fragment 100
tellLn :: (MonadWriter Text m) => Text -> m ()
tellLn s = tell s >> tell "\n"
@@ -49,8 +72,10 @@ type A = Commented SourceSpan
type I = Identity
--- | Current local state information while traversing the tree.
data St = St
+
+-- | Current local state information while traversing the tree.
+data Fmt = Fmt
{ indentLevel :: Int,
pendingLine :: Text
}
@@ -58,16 +83,16 @@ data St = St
newtype M a = M {unM :: RWS () () (St, Files) a}
deriving newtype (Functor, Applicative, Monad, MonadState (St, Files))
-newtype FormattedWriter a = FormattedWriter (RWS () Text St a)
- deriving newtype (Functor, Applicative, Monad, MonadState St)
+newtype FormattedWriter a = FormattedWriter (RWS () Text Fmt a)
+ deriving newtype (Functor, Applicative, Monad, MonadState Fmt)
-indented :: FormattedWriter a -> FormattedWriter a
+indented :: FileM a -> FileM a
indented fn = do
- modify (\(St id p) -> St (id + 1) p)
- fn <* modify (\(St id p) -> St (id - 1) p)
+ textM (modify (\(Fmt id p) -> Fmt (id + 1) p))
+ fn <* textM (modify (\(Fmt id p) -> Fmt (id - 1) p))
execFormattedWriter :: FormattedWriter a -> Text
-execFormattedWriter ((>> flush) -> FormattedWriter rws) = snd $ execRWS rws () (St 0 "")
+execFormattedWriter ((>> flush) -> FormattedWriter rws) = snd $ execRWS rws () (Fmt 0 "")
flush :: FormattedWriter ()
flush = do
@@ -75,23 +100,68 @@ flush = do
modify $ \s -> s {pendingLine = ""}
tell p
-data Files = Files
- { filepaths :: Map FilePath (Map FilePosition (FormattedWriter ()))
+newtype FileFragments = FileFragments (Map Fragment (FormattedWriter ()))
+
+instance Semigroup FileFragments where
+ (FileFragments m1) <> (FileFragments m2) = FileFragments (Map.unionWith (>>) m1 m2)
+
+instance Monoid FileFragments where
+ mempty = FileFragments mempty
+
+newtype CFileState = CFileState
+ { includedFiles :: Set String
}
-withFileAt :: FilePath -> FilePosition -> FormattedWriter () -> M ()
-withFileAt fp pos wr = do
+newtype FileM a = FileM {unFileM :: RWS Fragment FileFragments CFileState a}
+ deriving newtype (Functor, Applicative, Monad, MonadWriter FileFragments, MonadReader Fragment, MonadState CFileState)
+
+execFileM :: FileM a -> Text
+execFileM fm =
+ let (_, FileFragments mp) = execRWS (unFileM fm) hF (CFileState mempty)
+ in ( execFormattedWriter
+ . sequence_
+ . Map.elems
+ )
+ mp
+
+requireInclude :: String -> FileM ()
+requireInclude file = do
+ b <- (Set.member file) <$> gets includedFiles
+ unless b $ do
+ under hF $
+ text $
+ Text.pack $
+ printf "#include <%s>\n" file
+ modify $ \s -> s {includedFiles = Set.insert file (includedFiles s)}
+
+-- | Writes text to the current fragment context
+text :: Text -> FileM ()
+text t = flip tellF_ t =<< ask
+
+-- | Writes text to the current fragment context
+textM :: FormattedWriter () -> FileM ()
+textM t = flip tellFM_ t =<< ask
+
+-- | Executes a file monad within a different fragment.
+under :: Fragment -> FileM () -> FileM ()
+under fr = local (const fr)
+
+tellF_ :: Fragment -> Text -> FileM ()
+tellF_ fp txt = tell $ FileFragments $ Map.singleton fp (tell txt)
+
+tellFM_ :: Fragment -> FormattedWriter () -> FileM ()
+tellFM_ fp txtM = tell $ FileFragments $ Map.singleton fp txtM
+
+newtype Files = Files
+ { filepaths :: Map FilePath (FileM ())
+ }
+
+withFile :: FilePath -> FileM () -> M ()
+withFile fp fn = do
modify
( second $ \(Files {filepaths = fps}) ->
Files
- { filepaths =
- Map.alter
- ( \(fromMaybe mempty -> posMap) ->
- Just $
- Map.alter (Just . (>> wr) . fromMaybe (return ())) pos posMap
- )
- fp
- fps
+ { filepaths = Map.alter (Just . (>> fn) . fromMaybe (return ())) fp fps
}
)
@@ -106,7 +176,7 @@ instance MonadWriter Text FormattedWriter where
tell pending
tell line
tell "\n"
- modify $ \s -> s {pendingLine = last lines}
+ modify $ \s -> s {pendingLine = pendingLine s <> last lines}
listen (FormattedWriter fn) = FormattedWriter $ listen fn
@@ -143,13 +213,7 @@ cBackend =
toTranspileResult :: Files -> TranspileResult
toTranspileResult Files {filepaths = fps} =
- TranspileResult $
- fmap
- ( execFormattedWriter
- . sequence_
- . Map.elems
- )
- fps
+ TranspileResult $ fmap execFileM fps
transpile ::
CBackendFlags ->
@@ -162,21 +226,28 @@ transpile
cSourceOut = sourceFile
}
()
- fiddleUnit = toTranspileResult . snd . fst $ execRWS (unM run) () (St 0 "", Files mempty)
+ fiddleUnit = toTranspileResult . snd . fst $ execRWS (unM run) () (St, Files mempty)
where
run :: M ()
run = do
- withFileAt headerFile headerPos $ do
- tell $ "#ifndef " <> headerGuard <> "\n"
- tell $ "#define " <> headerGuard <> "\n"
+ withFile headerFile $ do
+ textM $ do
+ tell $ "#ifndef " <> headerGuard <> "\n"
+ tell $ "#define " <> headerGuard <> "\n\n"
+ tell "#include <stdint.h>\n"
+
+ -- Pad out the implementation
+ under iF $ text "\n"
walk (transpileWalk sourceFile headerFile) fiddleUnit ()
- withFileAt headerFile footerPos $ tell headerFinal
- withFileAt headerFile headerPos $ do
- tell "\n#include <stdint.h>\n"
+ withFile headerFile $ do
+ under hF $
+ textM $ do
+ tell "\nstatic_assert(true); // https://github.com/clangd/clangd/issues/1167\n"
- tell "\nstatic_assert(true); // https://github.com/clangd/clangd/issues/1167\n"
+ under fF $
+ text headerFinal
headerFinal = "\n#endif /* " <> headerGuard <> " */\n"
@@ -208,43 +279,210 @@ ensureNL = do
tell p
tell "\n"
-pad :: (IsString t, MonadWriter t m) => m a -> m a
-pad f = tell "\n" *> f <* tell "\n"
+pad :: FileM () -> FileM ()
+pad f = text "\n" *> f <* text "\n"
+
+writeStaticAssert :: Text -> String -> N Bytes -> FileM ()
+writeStaticAssert structName regname off = do
+ requireInclude "stddef.h"
+ text $
+ Text.pack $
+ printf
+ "\n_Static_assert(offsetof(%s, %s) == 0x%x, \"Offset wrong\");\n"
+ structName
+ regname
+ off
+
+sizeToType :: N Bytes -> Maybe String
+sizeToType = \case
+ 1 -> Just "uint8_t"
+ 2 -> Just "uint16_t"
+ 4 -> Just "uint32_t"
+ 8 -> Just "uint64_t"
+ _ -> Nothing
+
+selectByModifier :: Modifier f an -> (a, a) -> [a]
+selectByModifier mod (getter, setter) =
+ case mod of
+ (ModifierKeyword Rw _) -> [getter, setter]
+ (ModifierKeyword Ro _) -> [getter]
+ (ModifierKeyword Wo _) -> [setter]
+ (ModifierKeyword Pr _) -> []
+
+writeRegGet :: StructName -> QRegMetadata True -> FileM ()
+writeRegGet
+ structType
+ ( QRegMetadata
+ { regSpan =
+ Present
+ FieldSpan
+ { size = size
+ },
+ regFullPath = fullPath
+ }
+ ) = do
+ let fnName = "get_" <> qualifiedPathToIdentifier fullPath
+ returnType = sizeToType size
+ fieldName = NonEmpty.last fullPath
+
+ case returnType of
+ Just rt -> do
+ textM $ do
+ tell $
+ Text.pack $
+ printf "static inline %s %s(const %s* o) {\n" rt fnName structType
+ tell $ Text.pack $ printf " return o->%s;\n" fieldName
+ tell "}\n\n"
+ Nothing ->
+ -- Return type is not defined, fallback to byte-by-byte copy.
+ textM $ do
+ tell $
+ Text.pack $
+ printf
+ "static inline void %s(%s* o, uint8_t out[%d]) {\n"
+ fnName
+ structType
+ size
+ forM_ [0 .. size - 1] $ \i ->
+ tell $ Text.pack $ printf " out[%d] = o->%s[%d];\n" i fieldName i
+ tell "}\n\n"
+
+writeRegSet :: StructName -> QRegMetadata True -> FileM ()
+writeRegSet
+ structType
+ ( QRegMetadata
+ { regSpan =
+ Present
+ FieldSpan
+ { size = size
+ },
+ regFullPath = fullPath
+ }
+ ) = do
+ let fnName = "set_" <> qualifiedPathToIdentifier fullPath
+ setType = sizeToType size
+ fieldName = NonEmpty.last fullPath
+
+ case setType of
+ Just rt -> do
+ textM $ do
+ tell $
+ Text.pack $
+ printf "static inline void %s(%s* o, %s v) {\n" fnName structType rt
+ tell $ Text.pack $ printf " o->%s = v;\n" fieldName
+ tell "}\n\n"
+ Nothing ->
+ -- Return type is not defined, fallback to byte-by-byte copy.
+ textM $ do
+ tell $
+ Text.pack $
+ printf
+ "static inline void %s(%s* o, const uint8_t in[%d]) {\n"
+ fnName
+ structType
+ size
+ forM_ [0 .. size - 1] $ \i ->
+ tell $ Text.pack $ printf " o->%s[%d] = in[%d];\n" fieldName i i
+ tell "}\n\n"
+
+pattern DefinedBitsP :: String -> NonEmpty String -> N Bits -> RegisterBitsDecl Checked f a
+pattern DefinedBitsP bitsName bitsFullPath offset <-
+ ( DefinedBits
+ { qBitsMetadata =
+ Present
+ QBitsMetadata
+ { bitsSpan =
+ Present
+ FieldSpan
+ { offset = offset
+ },
+ bitsFullPath = (NonEmpty.last &&& id -> (bitsName, bitsFullPath))
+ }
+ }
+ )
+
+writeRegisterBody :: StructName -> QRegMetadata True -> RegisterBody Checked I A -> FileM ()
+writeRegisterBody structName regmeta = walk_ registerWalk
+ where
+ registerWalk :: forall t. (Walk t, Typeable t) => t I A -> FileM ()
+ registerWalk t = case () of
+ ()
+ | (Just (DefinedBitsP bitsName fullPath offset)) <- castTS t ->
+ text $
+ Text.pack $
+ printf
+ "// Emit bits %s (%s) at %d\n"
+ bitsName
+ (qualifiedPathToIdentifier fullPath)
+ offset
+ _ -> return ()
-structBody :: ObjTypeBody Checked I A -> FormattedWriter ()
-structBody (ObjTypeBody _ decls _) = do
+ castTS ::
+ forall (t' :: SynTree) (t :: StagedSynTree) (f :: Type -> Type) (a :: Type).
+ ( Typeable t',
+ Typeable t,
+ Typeable f,
+ Typeable a
+ ) =>
+ t' f a ->
+ Maybe (t Checked f a)
+ castTS = cast
+
+writeImplementation ::
+ StructName ->
+ QRegMetadata True ->
+ Modifier f a ->
+ Maybe (RegisterBody Checked I A) ->
+ FileM ()
+
+-- | Register is just padding, don't emit anything
+writeImplementation _ (regIsPadding -> True) _ _ = return ()
+writeImplementation structName qMeta mod bod = do
+ unless (regIsUnnamed qMeta) $
+ sequence_ $
+ selectByModifier mod (writeRegGet structName qMeta, writeRegSet structName qMeta)
+
+ mapM_ (writeRegisterBody structName qMeta) bod
+
+structBody :: StructName -> ObjTypeBody Checked I A -> FileM ()
+structBody structName (ObjTypeBody _ decls _) = do
forM_ decls $ \(Directed _ decl _) ->
case decl of
RegisterDecl
- { regSpan = Present (FieldSpan _ sz),
+ { qRegMeta = Present regMetadata,
regIdent = Guaranteed (identToString -> i),
+ regModifier = Guaranteed mod,
+ regBody = bod,
regAnnot = ann
} -> do
- emitDocComments ann
- tell (sizeToField i sz)
- tell ";\n"
- ReservedDecl
- { regSpan = Present (FieldSpan _ sz),
- reservedIdent = Present i,
- reservedAnnot = ann
- } -> do
- emitDocComments ann
- tell (sizeToField i sz)
- tell ";\n"
+ let (Present (FieldSpan off sz)) = regSpan regMetadata
+
+ textM $ do
+ emitDocComments ann
+ tell (sizeToField i sz)
+ tell ";\n"
+
+ under aF $
+ writeStaticAssert structName i off
+
+ under iF $
+ writeImplementation structName regMetadata mod bod
TypeSubStructure
{ subStructureBody = Identity bod,
subStructureName = mname
} -> do
- tell $ case objBodyType bod of
- Union {} -> "union "
- Struct {} -> "struct "
+ text $
+ case objBodyType bod of
+ Union {} -> "union "
+ Struct {} -> "struct "
- body $ structBody bod
+ body $ structBody structName bod
- forM_ mname $ \name ->
- tell (Text.pack $ identToString name)
+ textM $ do
+ forM_ mname $ \name ->
+ tell (Text.pack $ identToString name)
- tell ";\n"
+ tell ";\n"
where
sizeToField (Text.pack -> f) = \case
1 -> "volatile uint8_t " <> f
@@ -253,24 +491,24 @@ structBody (ObjTypeBody _ decls _) = do
8 -> "volatile uint64_t " <> f
n -> "volatile uint8_t " <> f <> "[" <> Text.pack (show n) <> "]"
-union :: Text -> FormattedWriter () -> FormattedWriter ()
+union :: Text -> FileM () -> FileM ()
union identifier fn = do
- tell "#pragma pack(push, 1)\n"
- tell $ "union " <> identifier <> " "
+ text "#pragma pack(push, 1)\n"
+ text $ "union " <> identifier <> " "
body fn
- tell ";\n"
- tell "#pragma pack(pop)\n"
+ text ";\n"
+ text "#pragma pack(pop)\n"
-struct :: Text -> FormattedWriter () -> FormattedWriter ()
+struct :: Text -> FileM () -> FileM ()
struct identifier fn = do
- tell "#pragma pack(push, 1)\n"
- tell $ "struct " <> identifier <> " "
+ text "#pragma pack(push, 1)\n"
+ text $ "struct " <> identifier <> " "
body fn
- tell ";\n"
- tell "#pragma pack(pop)\n"
+ text ";\n"
+ text "#pragma pack(pop)\n"
-body :: FormattedWriter a -> FormattedWriter a
-body f = tell "{\n" *> indented f <* (ensureNL >> tell "}")
+body :: FileM a -> FileM a
+body f = text "{\n" *> indented f <* textM (ensureNL >> tell "}")
identifierFor :: (ExportableDecl d) => d -> Text
identifierFor = qualifiedPathToIdentifier . metadataFullyQualifiedPath . getMetadata
@@ -313,14 +551,29 @@ transpileWalk _ headerFile t _ = case () of
Union {} -> union
Struct {} -> struct
- withFileAt headerFile middlePos $ do
- pad $ do
- emitDocComments a
- structureType (identifierFor (unwrap metadata)) $ do
- structBody objTypeBody
+ withFile headerFile $ do
+ under sF $ do
+ pad $ do
+ textM $ emitDocComments a
+ let structName = identifierFor (unwrap metadata)
+ structureType structName $ do
+ structBody structName objTypeBody
return Stop
+ () | Just (getExportedObjectDecl -> Just e) <- castTS t -> do
+ let qname = qualifiedPathToIdentifier (metadataFullyQualifiedPath (getMetadata e))
+ withFile headerFile $
+ under fF $ do
+ text "#define "
+ text qname
+ text $ Text.pack $ printf " ((%s*)0x%08x)\n" (toLiteralTypeName (exportedObjectDeclType e)) (exportedObjectDeclLocation e)
+
+ return Stop
_ -> return (Continue ())
where
+ toLiteralTypeName :: ReferencedObjectType -> Text
+ toLiteralTypeName (ReferencedObjectType str) = qualifiedPathToIdentifier str
+ toLiteralTypeName (ArrayObjectType ro _) = toLiteralTypeName ro
+
castTS ::
forall (t' :: SynTree) (t :: StagedSynTree) (f :: Type -> Type) (a :: Type).
( Typeable t',
@@ -331,3 +584,7 @@ transpileWalk _ headerFile t _ = case () of
t' f a ->
Maybe (t Checked f a)
castTS = cast
+
+ getExportedObjectDecl :: FiddleDecl Checked I A -> Maybe ExportedObjectDecl
+ getExportedObjectDecl (ObjectDecl {objectQualificationMetadata = Identity (Present decl)}) = Just decl
+ getExportedObjectDecl _ = Nothing