copilot-c99 3.7 → 4.8
raw patch · 18 files changed
Files
- CHANGELOG +135/−20
- copilot-c99.cabal +55/−23
- src/Copilot/Compile/C99.hs +3/−2
- src/Copilot/Compile/C99/CodeGen.hs +307/−139
- src/Copilot/Compile/C99/Compile.hs +304/−5
- src/Copilot/Compile/C99/Compile/Internal.hs +0/−159
- src/Copilot/Compile/C99/Error.hs +37/−0
- src/Copilot/Compile/C99/Expr.hs +490/−0
- src/Copilot/Compile/C99/External.hs +40/−32
- src/Copilot/Compile/C99/Name.hs +61/−0
- src/Copilot/Compile/C99/Representation.hs +21/−0
- src/Copilot/Compile/C99/Settings.hs +12/−4
- src/Copilot/Compile/C99/Settings/Internal.hs +0/−12
- src/Copilot/Compile/C99/Translate.hs +0/−169
- src/Copilot/Compile/C99/Type.hs +52/−0
- src/Copilot/Compile/C99/Util.hs +0/−64
- tests/Main.hs +18/−0
- tests/Test/Copilot/Compile/C99.hs +1042/−0
CHANGELOG view
@@ -1,3 +1,118 @@+2026-07-07+ * Version bump (4.8). (#745)++2026-05-07+ * Version bump (4.7.1). (#730)++2026-03-07+ * Version bump (4.7). (#714)+ * Raise error when compiling empty arrays or structs. (#695)++2026-01-07+ * Version bump (4.6.1). (#705)++2025-11-07+ * Version bump (4.6). (#679)++2025-09-07+ * Version bump (4.5.1). (#666)++2025-07-07+ * Version bump (4.5). (#642)++2025-05-07+ * Version bump (4.4). (#618)++2025-03-07+ * Version bump (4.3). (#604)++2025-01-07+ * Version bump (4.2). (#577)+ * Remove uses of Copilot.Core.Expr.UExpr.uExprType. (#565)+ * Allow using same trigger name in multiple declarations. (#296)+ * Bump upper version constraint on filepath. (#570)++2024-11-07+ * Version bump (4.1). (#561)+ * Standardize changelog format. (#550)++2024-09-07+ * Version bump (4.0). (#532)+ * Add support for array updates. (#36)++2024-07-07+ * Version bump (3.20). (#522)+ * Add support for struct field updates. (#520)++2024-05-07+ * Version bump (3.19.1). (#512)++2024-03-07+ * Version bump (3.19). (#504)++2024-01-07+ * Version bump (3.18.1). (#493)++2024-01-07+ * Version bump (3.18). (#487)+ * Change return type of main generated for tests. (#468)+ * Print constants in tests using portable suffixes. (#471)+ * Pass output arrays as arguments to trigger argument functions. (#431)+ * Compliance with MISRA C 2023 / MISRA C 2012. (#472)++2023-11-07+ * Version bump (3.17). (#466)+ * Replace uses of deprecated functions. (#457)++2023-09-07+ * Version bump (3.16.1). (#455)+ * Clean code. (#453)++2023-07-07+ * Version bump (3.16). (#448)+ * Introduce testing infrastructure. (#413)++2023-05-07+ * Version bump (3.15). (#438)++2023-03-07+ * Version bump (3.14). (#422)++2023-01-07+ * Version bump (3.13). (#406)+ * Declare local array variables in generated guards as pointers. (#401)+ * Use pointer to pass output array as argument to generators. (#386)++2022-11-07+ * Version bump (3.12). (#389)+ * Removed deprecated flag from cabal file. (#380)+ * Generate type declarations in separate header file. (#373)++2022-09-07+ * Version bump (3.11). (#376)+ * Update to support language-c99-0.2.0. (#371)+ * Fix error handling buffers in generated code for 'step'. (#314)++2022-07-07+ * Version bump (3.10). (#356)+ * Remove unnecessary dependencies from Cabal package. (#323)+ * Remove duplicated compiler option. (#328)+ * Pass structs by reference, not value, in handlers. (#305)+ * Relax version bounds of dependencies. (#335)+ * Update repo info in cabal file. (#333)++2022-05-06+ * Version bump (3.9). (#320)+ * Compliance with style guide (partial). (#316)+ * Translate math operations taking type into account. (#263)+ * Fix issue with delays of streams of structs or arrays. (#276)+ * Fix issue in C99 implementation of signum. (#278)++2022-03-07+ * Version bump (3.8). (#298)+ * Hide internal modules deprecated in Copilot 3.5. (#289)+ * Mark package as uncurated to avoid modification. (#288)+ 2022-01-07 * Version bump (3.7). (#287) * Guard against empty specs. (#274)@@ -34,31 +149,31 @@ * Completed the documentation. (#171) 2020-12-06- * Version bump (3.2).- * Implemented arrays in test driver (#176).- * Fixed nested array initialisation bug (#173).- * Fixed length of buffer allocation for n-dimensional arrays (#174).- * Fixed printing of long ints in test suite (#177).- * Fixed printing of unsigned ints in test suite (#177).- * Fixed '-Wsequence-point' warnings from GCC (#179).- * Split Property.hs (#180).- * Removed 'Test' from module paths (#181).- * Made compiletest take compiler options as an argument (#182).- * Fixed problem with property and empty string in driver CSV (#183).- * Added comma to output of driver to match the interpreter (#184).- * Implemented basic quickcheck based testing (#185).+ * Version bump (3.2). (#65)+ * Implemented arrays in test driver. (#176)+ * Fixed nested array initialisation bug. (#173)+ * Fixed length of buffer allocation for n-dimensional arrays. (#174)+ * Fixed printing of long ints in test suite. (#177)+ * Fixed printing of unsigned ints in test suite. (#177)+ * Fixed '-Wsequence-point' warnings from GCC. (#179)+ * Split Property.hs. (#180)+ * Removed 'Test' from module paths. (#181)+ * Made compiletest take compiler options as an argument. (#182)+ * Fixed problem with property and empty string in driver CSV. (#183)+ * Added comma to output of driver to match the interpreter. (#184)+ * Implemented basic quickcheck based testing. (#185) 2020-03-30- * Version bump (3.1.2)+ * Version bump (3.1.2). (#187) * Fixed bug where stream buffers are updated too soon. (#188) * Updated description of cabal package. (#192) 2019-12-23- * Version bump (3.1.1).- * Fixed bug with constant structs and arrays.(#200).+ * Version bump (3.1.1). (#191)+ * Fixed bug with constant structs and arrays. (#200) 2019-11-22- * Version bump (3.1).- * Remove ExternFun (#207).- * Fix bug in code generation for local expression (#198).- * Implement code generation for labels (trivially) (#199).+ * Version bump (3.1). (#46)+ * Remove ExternFun. (#207)+ * Fix bug in code generation for local expression. (#198)+ * Implement code generation for labels (trivially). (#199)
copilot-c99.cabal view
@@ -1,6 +1,6 @@ cabal-version : >= 1.10 name : copilot-c99-version : 3.7+version : 4.8 synopsis : A compiler for Copilot targeting C99. description : This package is a back-end from Copilot to C.@@ -27,39 +27,71 @@ , Alwyn Goodloe , Ivan Perez +x-curation: uncurated+ source-repository head type: git- location: git://github.com/Copilot-Language/copilot.git- subdir: lib/copilot-c99+ location: https://github.com/Copilot-Language/copilot.git+ subdir: copilot-c99 library default-language : Haskell2010 hs-source-dirs : src - -- The following -Wno-deprecations is temprary and related to issue- -- https://github.com/Copilot-Language/copilot/issues/237- -- It should be removed in a future version, when this library hides- -- internal modules.- ghc-options : -Wall -fwarn-tabs -Wno-deprecations+ ghc-options : -Wall build-depends : base >= 4.9 && < 5- , containers >= 0.4 && < 0.7 , directory >= 1.3 && < 1.4- , filepath >= 1.4 && < 1.5- , mtl >= 2.2 && < 2.3+ , filepath >= 1.4 && < 1.6+ , mtl >= 2.2 && < 2.4 , pretty >= 1.1 && < 1.2 - , copilot-core >= 3.7 && < 3.8- , language-c99 >= 0.1.1 && < 0.2- , language-c99-util >= 0.1.1 && < 0.2- , language-c99-simple >= 0.1.1 && < 0.2+ , copilot-core >= 4.8 && < 4.9+ , language-c99 >= 0.2.0 && < 0.3+ , language-c99-simple >= 0.3 && < 0.4 exposed-modules : Copilot.Compile.C99- , Copilot.Compile.C99.Translate- , Copilot.Compile.C99.Util- , Copilot.Compile.C99.CodeGen- , Copilot.Compile.C99.External- , Copilot.Compile.C99.Compile- , Copilot.Compile.C99.Settings - other-modules : Copilot.Compile.C99.Compile.Internal- , Copilot.Compile.C99.Settings.Internal+ other-modules : Copilot.Compile.C99.Expr+ , Copilot.Compile.C99.Type+ , Copilot.Compile.C99.Error+ , Copilot.Compile.C99.Name+ , Copilot.Compile.C99.CodeGen+ , Copilot.Compile.C99.External+ , Copilot.Compile.C99.Compile+ , Copilot.Compile.C99.Settings+ , Copilot.Compile.C99.Representation++test-suite unit-tests+ type:+ exitcode-stdio-1.0++ main-is:+ Main.hs++ other-modules:+ Test.Copilot.Compile.C99++ build-depends:+ base+ , directory+ , HUnit+ , QuickCheck+ , pretty+ , process+ , random+ , test-framework+ , test-framework-hunit+ , test-framework-quickcheck2+ , unix++ , copilot-core+ , copilot-c99++ hs-source-dirs:+ tests++ default-language:+ Haskell2010++ ghc-options:+ -Wall
src/Copilot/Compile/C99.hs view
@@ -6,5 +6,6 @@ , mkDefaultCSettings ) where -import Copilot.Compile.C99.Compile.Internal-import Copilot.Compile.C99.Settings.Internal+-- Internal imports+import Copilot.Compile.C99.Compile ( compile, compileWith )+import Copilot.Compile.C99.Settings ( CSettings (..), mkDefaultCSettings )
src/Copilot/Compile/C99/CodeGen.hs view
@@ -1,179 +1,347 @@ {-# LANGUAGE GADTs #-}-{-# LANGUAGE ScopedTypeVariables #-} -- | High-level translation of Copilot Core into C99. module Copilot.Compile.C99.CodeGen- {-# DEPRECATED "This module will be hidden in future versions." #-}- where+ (+ -- * Externs+ mkExtCpyDecln+ , mkExtDecln -import Control.Monad.State (runState)-import Data.List (union, unzip4)-import Data.Typeable (Typeable)+ -- * Type declarations+ , mkStructDecln+ , mkStructForwDecln + -- * Ring buffers+ , mkBuffDecln+ , mkIndexDecln+ , mkAccessDecln++ -- * Stream generators+ , mkGenFun+ , mkGenFunArray++ -- * Monitor processing+ , mkStep+ )+ where++-- External imports+import Control.Monad.State ( runState )+import Data.List ( unzip4 )+import qualified Data.List.NonEmpty as NonEmpty import qualified Language.C99.Simple as C -import Copilot.Core-import Copilot.Compile.C99.Util-import Copilot.Compile.C99.External-import Copilot.Compile.C99.Settings-import Copilot.Compile.C99.Translate+-- Internal imports: Copilot+import Copilot.Core ( Expr (..), Id, Stream (..), Struct (..), Trigger (..),+ Type (..), UExpr (..), Value (..), fieldName, typeSize ) --- | Write a declaration for a generator function.-gendecln :: String -> Type a -> C.Decln-gendecln name ty = C.FunDecln Nothing cty name [] where- cty = C.decay $ transtype ty+-- Internal imports+import Copilot.Compile.C99.Error ( impossible )+import Copilot.Compile.C99.Expr ( constArray, transExpr )+import Copilot.Compile.C99.External ( External (..) )+import Copilot.Compile.C99.Name ( argNames, argTempNames,+ generatorName, guardName,+ indexName, streamAccessorName,+ streamName )+import Copilot.Compile.C99.Settings ( CSettings,+ cSettingsStepFunctionName )+import Copilot.Compile.C99.Type ( transType )+import Copilot.Compile.C99.Representation ( UniqueTrigger (..) ) --- | Write a generator function for a stream.-genfun :: String -> Expr a -> Type a -> C.FunDef-genfun name expr ty = C.FunDef cty name [] cvars [C.Return $ Just cexpr] where- cty = C.decay $ transtype ty- (cexpr, cvars) = runState (transexpr expr) mempty+-- * Externs -- | Make a extern declaration of a variable.-mkextdecln :: External -> C.Decln-mkextdecln (External name _ ty) = decln where- decln = C.VarDecln (Just C.Extern) cty name Nothing- cty = transtype ty+mkExtDecln :: External -> C.Decln+mkExtDecln (External name _ ty) = decln+ where+ decln = C.VarDecln (Just C.Extern) cTy name Nothing+ cTy = transType ty -- | Make a declaration for a copy of an external variable.-mkextcpydecln :: External -> C.Decln-mkextcpydecln (External name cpyname ty) = decln where- cty = transtype ty- decln = C.VarDecln (Just C.Static) cty cpyname Nothing+mkExtCpyDecln :: External -> C.Decln+mkExtCpyDecln (External _name cpyName ty) = decln+ where+ decln = C.VarDecln (Just C.Static) cTy cpyName Nothing+ cTy = transType ty +-- * Type declarations++-- | Write a struct declaration based on its definition.+mkStructDecln :: Struct a => Type a -> C.Decln+mkStructDecln (Struct x) = C.TypeDecln struct+ where+ struct = C.TypeSpec $ C.StructDecln (Just $ typeName x) fields+ fields = NonEmpty.fromList $ map mkField (toValues x)++ mkField :: Value a -> C.FieldDecln+ mkField (Value ty field) = C.FieldDecln (transType ty) (fieldName field)++-- | Write a forward struct declaration.+mkStructForwDecln :: Struct a => Type a -> C.Decln+mkStructForwDecln (Struct x) = C.TypeDecln struct+ where+ struct = C.TypeSpec $ C.Struct (typeName x)++-- * Ring buffers+ -- | Make a C buffer variable and initialise it with the stream buffer.-mkbuffdecln :: Id -> Type a -> [a] -> C.Decln-mkbuffdecln sid ty xs = C.VarDecln (Just C.Static) cty name initvals where- name = streamname sid- cty = C.Array (transtype ty) (Just $ C.LitInt $ fromIntegral buffsize)- buffsize = length xs- initvals = Just $ C.InitArray $ map (C.InitExpr . constty ty) xs+mkBuffDecln :: Id -> Type a -> [a] -> C.Decln+mkBuffDecln sId ty xs = C.VarDecln (Just C.Static) cTy name initVals+ where+ name = streamName sId+ cTy = C.Array (transType ty) (Just $ C.LitInt $ fromIntegral buffSize)+ buffSize = length xs+ initVals = Just $ C.InitList $ constArray ty xs -- | Make a C index variable and initialise it to 0.-mkindexdecln :: Id -> C.Decln-mkindexdecln sid = C.VarDecln (Just C.Static) cty name initval where- name = indexname sid- cty = C.TypeSpec $ C.TypedefName "size_t"- initval = Just $ C.InitExpr $ C.LitInt 0+mkIndexDecln :: Id -> C.Decln+mkIndexDecln sId = C.VarDecln (Just C.Static) cTy name initVal+ where+ name = indexName sId+ cTy = C.TypeSpec $ C.TypedefName "size_t"+ initVal = Just $ C.InitExpr $ C.LitInt 0 --- | Define an accessor functions for the ring buffer associated with a stream-mkaccessdecln :: Id -> Type a -> [a] -> C.FunDef-mkaccessdecln sid ty xs = C.FunDef cty name params [] [C.Return (Just expr)]+-- | Define an accessor functions for the ring buffer associated with a stream.+mkAccessDecln :: Id -> Type a -> [a] -> C.FunDef+mkAccessDecln sId ty xs =+ C.FunDef static cTy name params [] [C.Return (Just expr)] where- cty = C.decay $ transtype ty- name = streamaccessorname sid- bufflength = C.LitInt $ fromIntegral $ length xs+ static = Just C.Static+ cTy = C.decay $ transType ty+ name = streamAccessorName sId++ -- We cast the buffer length to a size_t to make sure that there are no+ -- implicit conversions. This is a requirement for compliance with MISRA C+ -- (Rule 10.4).+ buffLength = C.Cast sizeT $ C.LitInt $ fromIntegral $ length xs+ sizeT = C.TypeName $ C.TypeSpec $ C.TypedefName "size_t" params = [C.Param (C.TypeSpec $ C.TypedefName "size_t") "x"]- index = (C.Ident (indexname sid) C..+ C.Ident "x") C..% bufflength- expr = C.Index (C.Ident (streamname sid)) index+ index = (C.Ident (indexName sId) C..+ C.Ident "x") C..% buffLength+ expr = C.Index (C.Ident (streamName sId)) index --- | Writes the step function, that updates all streams.-mkstep :: CSettings -> [Stream] -> [Trigger] -> [External] -> C.FunDef-mkstep cSettings streams triggers exts =- C.FunDef void (cSettingsStepFunctionName cSettings) [] declns stmts where+-- * Stream generators - void = C.TypeSpec C.Void- stmts = map mkexcopy exts- ++ map mktriggercheck triggers- ++ tmpassigns- ++ bufferupdates- ++ indexupdates- (declns, tmpassigns, bufferupdates, indexupdates) =- unzip4 $ map mkupdateglobals streams+-- | Write a generator function for a stream.+mkGenFun :: String -> Expr a -> Type a -> C.FunDef+mkGenFun name expr ty =+ C.FunDef static cTy name [] cVars stmts+ where+ static = Just C.Static+ cTy = C.decay $ transType ty+ (cExpr, state') = runState (transExpr expr) (0, mempty, mempty)+ (_, cVars, stmts') = state'+ stmts = stmts' ++ [C.Return $ Just cExpr] - -- Write code to update global stream buffers and index.- mkupdateglobals :: Stream -> (C.Decln, C.Stmt, C.Stmt, C.Stmt)- mkupdateglobals (Stream sid buff expr ty) =- (tmpdecln, tmpassign, bufferupdate, indexupdate)- where- tmpdecln = C.VarDecln Nothing cty tmp_var Nothing+-- | Write a generator function for a stream that returns an array.+mkGenFunArray :: String -> String -> Expr a -> Type a -> C.FunDef+mkGenFunArray name nameArg expr ty@(Array _) =+ C.FunDef static funType name [ outputParam ] varDecls stmts+ where+ static = Just C.Static+ funType = C.TypeSpec C.Void - tmpassign = case ty of- Array _ -> C.Expr $ memcpy (C.Ident tmp_var) val size- where- size = C.LitInt $ fromIntegral $ tysize ty- _ -> C.Expr $ C.Ident tmp_var C..= val+ -- The output value is an array+ outputParam = C.Param cArrayType nameArg+ cArrayType = transType ty - bufferupdate = case ty of- Array _ -> C.Expr $ memcpy dest (C.Ident tmp_var) size+ -- Statements to execute as part of the generator. They include statements+ -- related to calculating the values of intermediate expressions, and+ -- statements related to copying the final value to the destination array.+ stmts = stmts' ++ [ copyStmt ]++ -- Output value, variable declarations needed, and other statements that+ -- must be executed to calculate intermediate values.+ (cExpr, state') = runState (transExpr expr) (0, mempty, mempty)+ (_, varDecls, stmts') = state'++ -- Copy expression to output argument.+ copyStmt = C.Expr $ memcpy (C.Ident nameArg) cExpr size+ size = C.LitInt (fromIntegral $ typeSize ty)+ C..* C.SizeOfType (C.TypeName $ tyElemName ty)++mkGenFunArray _name _nameArg _expr _ty =+ impossible "mkGenFunArray" "copilot-c99"++-- * Monitor processing++-- | Define the step function that updates all streams.+mkStep :: CSettings -> [Stream] -> [UniqueTrigger] -> [External] -> C.FunDef+mkStep cSettings streams triggers exts =+ C.FunDef Nothing void (cSettingsStepFunctionName cSettings) [] declns stmts+ where+ void = C.TypeSpec C.Void++ declns = streamDeclns+ ++ concat triggerDeclns++ stmts = map mkExCopy exts+ ++ triggerStmts+ ++ tmpAssigns+ ++ bufferUpdates+ ++ indexUpdates++ (streamDeclns, tmpAssigns, bufferUpdates, indexUpdates) =+ unzip4 $ map mkUpdateGlobals streams+ (triggerDeclns, triggerStmts) =+ unzip $ map mkTriggerCheck triggers++ -- Update the value of a variable with the result of calling a function that+ -- generates the next value in a stream expression. If the type of the+ -- variable is an array, then we cannot perform a direct C assignment, so+ -- we instead pass the variable as an output array to the function.+ updateVar :: C.Ident -> C.Ident -> Type a -> C.Expr+ updateVar varName genName (Array _) =+ C.Funcall (C.Ident genName) [C.Ident varName]+ updateVar varName genName _ =+ C.AssignOp C.Assign (C.Ident varName) (C.Funcall (C.Ident genName) [])++ -- Write code to update global stream buffers and index.+ mkUpdateGlobals :: Stream -> (C.Decln, C.Stmt, C.Stmt, C.Stmt)+ mkUpdateGlobals (Stream sId buff _expr ty) =+ (tmpDecln, tmpAssign, bufferUpdate, indexUpdate)+ where+ tmpDecln = C.VarDecln Nothing cTy tmpVar Nothing++ tmpAssign = C.Expr $ updateVar tmpVar (generatorName sId) ty++ bufferUpdate = case ty of+ Array _ -> C.Expr $ memcpy dest (C.Ident tmpVar) size+ where+ dest = C.Index buffVar indexVar+ size = C.LitInt+ (fromIntegral $ typeSize ty)+ C..* C.SizeOfType (C.TypeName (tyElemName ty))+ _ -> C.Expr $+ C.Index buffVar indexVar C..= C.Ident tmpVar++ indexUpdate = C.Expr $ indexVar C..= (incIndex C..% buffLength) where- dest = C.Index buff_var index_var- size = C.LitInt $ fromIntegral $ tysize ty- _ -> C.Expr $ C.Index buff_var index_var C..= (C.Ident tmp_var)+ -- We cast the buffer length and the literal one to a size_t to+ -- make sure that there are no implicit conversions. This is a+ -- requirement for compliance with MISRA C (Rule 10.4).+ buffLength = C.Cast sizeT $ C.LitInt $ fromIntegral $ length buff+ incIndex = indexVar C..+ C.Cast sizeT (C.LitInt 1)+ sizeT = C.TypeName $ C.TypeSpec $ C.TypedefName "size_t" - indexupdate = C.Expr $ index_var C..= (incindex C..% bufflength)- where- bufflength = C.LitInt $ fromIntegral $ length buff- incindex = index_var C..+ C.LitInt 1+ tmpVar = streamName sId ++ "_tmp"+ buffVar = C.Ident $ streamName sId+ indexVar = C.Ident $ indexName sId+ cTy = transType ty - tmp_var = streamname sid ++ "_tmp"- buff_var = C.Ident $ streamname sid- index_var = C.Ident $ indexname sid- val = C.Funcall (C.Ident $ generatorname sid) []- cty = transtype ty+ -- Make code that copies an external variable to its local one.+ mkExCopy :: External -> C.Stmt+ mkExCopy (External name cpyName ty) = C.Expr $ case ty of+ Array _ -> memcpy exVar locVar size+ where+ exVar = C.Ident cpyName+ locVar = C.Ident name+ size = C.LitInt (fromIntegral $ typeSize ty)+ C..* C.SizeOfType (C.TypeName (tyElemName ty)) - -- Make code that copies an external variable to its local one.- mkexcopy :: External -> C.Stmt- mkexcopy (External name cpyname ty) = C.Expr $ case ty of- Array _ -> memcpy exvar locvar size where- exvar = C.Ident cpyname- locvar = C.Ident name- size = C.LitInt $ fromIntegral $ tysize ty- _ -> C.Ident cpyname C..= C.Ident name+ _ -> C.Ident cpyName C..= C.Ident name - -- Make if-statement to check the guard, call the trigger if necessary.- mktriggercheck :: Trigger -> C.Stmt- mktriggercheck (Trigger name guard args) = C.If guard' firetrigger where- guard' = C.Funcall (C.Ident $ guardname name) []- firetrigger = [C.Expr $ C.Funcall (C.Ident name) args'] where- args' = take (length args) (map argcall (argnames name))- argcall name = C.Funcall (C.Ident name) []+ -- Make if-statement to check the guard, call the handler if necessary.+ -- This returns two things:+ --+ -- * A list of Declns for temporary variables, one for each argument that+ -- the handler function accepts. For example, if a handler function takes+ -- three arguments, the list of Declns might look something like this:+ --+ -- @+ -- int8_t handler_arg_temp0;+ -- int16_t handler_arg_temp1;+ -- struct s handler_arg_temp2;+ -- @+ --+ -- * A Stmt representing the if-statement. Continuing the example above,+ -- the if-statement would look something like this:+ --+ -- @+ -- if (handler_guard()) {+ -- handler_arg_temp0 = handler_arg0();+ -- handler_arg_temp1 = handler_arg1();+ -- handler_arg_temp2 = handler_arg2();+ -- handler(handler_arg_temp0, handler_arg_temp1, &handler_arg_temp2);+ -- }+ -- @+ --+ -- We create temporary variables because:+ --+ -- 1. We want to pass structs by reference intead of by value. To this end,+ -- we use C's & operator to obtain a reference to a temporary variable+ -- of a struct type and pass that to the handler function.+ --+ -- 2. Assigning a struct to a temporary variable defensively ensures that+ -- any modifications that the handler called makes to the struct argument+ -- will not affect the internals of the monitoring code.+ mkTriggerCheck :: UniqueTrigger -> ([C.Decln], C.Stmt)+ mkTriggerCheck (UniqueTrigger uniqueName (Trigger name _guard args)) =+ (aTmpDeclns, triggerCheckStmt)+ where+ aTmpDeclns :: [C.Decln]+ aTmpDeclns = zipWith declare args aTempNames+ where+ declare :: UExpr -> C.Ident -> C.Decln+ declare (UExpr ty _) tmpVar =+ C.VarDecln Nothing (transType ty) tmpVar Nothing - -- Write a call to the memcpy function.- memcpy :: C.Expr -> C.Expr -> C.Expr -> C.Expr- memcpy dest src size = C.Funcall (C.Ident "memcpy") [dest, src, size]+ triggerCheckStmt :: C.Stmt+ triggerCheckStmt = C.If guard' fireTrigger+ where+ guard' = C.Funcall (C.Ident $ guardName uniqueName) [] + -- The body of the if-statement. This consists of statements that+ -- assign the values of the temporary variables, following by a+ -- final statement that passes the temporary variables to the+ -- handler function.+ -- Note that we call 'name' here instead of 'uniqueName', as 'name'+ -- is the name of the actual external function.+ fireTrigger = map C.Expr argAssigns+ ++ [C.Expr $+ C.Funcall (C.Ident name)+ (zipWith passArg aTempNames args)]+ where+ -- List of assignments of values of temporary variables.+ argAssigns :: [C.Expr]+ argAssigns = zipWith3 assign aTempNames aArgNames args --- | Write a struct declaration based on its definition.-mkstructdecln :: Struct a => Type a -> C.Decln-mkstructdecln (Struct x) = C.TypeDecln struct where- struct = C.TypeSpec $ C.StructDecln (Just $ typename x) fields- fields = map mkfield (toValues x)+ assign :: C.Ident -> C.Ident -> UExpr -> C.Expr+ assign aTempName aArgName (UExpr ty _) =+ updateVar aTempName aArgName ty - mkfield :: Value a -> C.FieldDecln- mkfield (Value ty field) = C.FieldDecln (transtype ty) (fieldname field)+ aArgNames :: [C.Ident]+ aArgNames = take (length args) (argNames uniqueName) --- | Write a forward struct declaration.-mkstructforwdecln :: Struct a => Type a -> C.Decln-mkstructforwdecln (Struct x) = C.TypeDecln struct where- struct = C.TypeSpec $ C.Struct (typename x)+ -- Build an expression to pass a temporary variable as argument+ -- to a trigger handler.+ --+ -- We need to pass a reference to the variable in some cases,+ -- so we also need the type of the expression, which is enclosed+ -- in the second argument, an UExpr.+ passArg :: String -> UExpr -> C.Expr+ passArg aTempName (UExpr ty _) =+ case ty of+ -- Special case for Struct to pass reference to temporary+ -- struct variable to handler. (See the comments for+ -- mktriggercheck for details.)+ Struct _ -> C.UnaryOp C.Ref $ C.Ident aTempName+ _ -> C.Ident aTempName --- | List all types of an expression, returns items uniquely.-exprtypes :: Typeable a => Expr a -> [UType]-exprtypes e = case e of- Const ty _ -> typetypes ty- Local ty1 ty2 _ e1 e2 -> typetypes ty1 `union` typetypes ty2- `union` exprtypes e1 `union` exprtypes e2- Var ty _ -> typetypes ty- Drop ty _ _ -> typetypes ty- ExternVar ty _ _ -> typetypes ty- Op1 _ e1 -> exprtypes e1- Op2 _ e1 e2 -> exprtypes e1 `union` exprtypes e2- Op3 _ e1 e2 e3 -> exprtypes e1 `union` exprtypes e2 `union` exprtypes e3- Label ty _ _ -> typetypes ty+ aTempNames :: [String]+ aTempNames = take (length args) (argTempNames uniqueName) --- | List all types of a type, returns items uniquely.-typetypes :: Typeable a => Type a -> [UType]-typetypes ty = case ty of- Array ty' -> typetypes ty' `union` [UType ty]- Struct x -> concatMap (\(Value ty' _) -> typetypes ty') (toValues x) `union` [UType ty]- _ -> [UType ty]+-- * Auxiliary functions --- | Collect all expression of a list of streams and triggers and wrap them--- into an UEXpr.-gatherexprs :: [Stream] -> [Trigger] -> [UExpr]-gatherexprs streams triggers = map streamexpr streams- ++ concatMap triggerexpr triggers where- streamexpr (Stream _ _ expr ty) = UExpr ty expr- triggerexpr (Trigger _ guard args) = UExpr Bool guard : args+-- Write a call to the memcpy function.+memcpy :: C.Expr -> C.Expr -> C.Expr -> C.Expr+memcpy dest src size = C.Funcall (C.Ident "memcpy") [dest, src, size] +-- Translate a Copilot type to a C99 type, handling arrays especially.+--+-- If the given type is an array (including multi-dimensional arrays), the+-- type is that of the elements in the array. Otherwise, it is just the+-- equivalent representation of the given type in C.+tyElemName :: Type a -> C.Type+tyElemName ty = case ty of+ Array ty' -> tyElemName ty'+ _ -> transType ty
src/Copilot/Compile/C99/Compile.hs view
@@ -1,11 +1,310 @@+{-# LANGUAGE GADTs #-} -- | Compile Copilot specifications to C99 code. module Copilot.Compile.C99.Compile- {-# DEPRECATED "This module will be hidden in future versions." #-} ( compile , compileWith- , CSettings(..)- , mkDefaultCSettings ) where -import Copilot.Compile.C99.Compile.Internal-import Copilot.Compile.C99.Settings.Internal+-- External imports+import Data.List ( nub, nubBy, union )+import Data.Maybe ( mapMaybe )+import Data.Type.Equality ( testEquality, (:~:)(Refl) )+import Data.Typeable ( Typeable )+import Language.C99.Pretty ( pretty )+import qualified Language.C99.Simple as C+import System.Directory ( createDirectoryIfMissing )+import System.Exit ( exitFailure )+import System.FilePath ( (</>) )+import System.IO ( hPutStrLn, stderr )+import Text.PrettyPrint ( render )++-- Internal imports: Copilot+import Copilot.Core ( Expr (..), Spec (..), Stream (..), Struct (..),+ Trigger (..), Type (..), UExpr (..), UType (..),+ Value (..) )++-- Internal imports+import Copilot.Compile.C99.CodeGen ( mkAccessDecln, mkBuffDecln,+ mkExtCpyDecln, mkExtDecln,+ mkGenFun, mkGenFunArray,+ mkIndexDecln, mkStep,+ mkStructDecln, mkStructForwDecln )+import Copilot.Compile.C99.External ( External, gatherExts )+import Copilot.Compile.C99.Name ( argNames, generatorName,+ generatorOutputArgName, guardName )+import Copilot.Compile.C99.Settings ( CSettings,+ cSettingsOutputDirectory,+ cSettingsStepFunctionName,+ mkDefaultCSettings )+import Copilot.Compile.C99.Type ( transType )+import Copilot.Compile.C99.Representation ( UniqueTrigger (..),+ mkUniqueTriggers )++-- | Compile a specification to a .h and a .c file.+--+-- The first argument is the settings for the C code generated.+--+-- The second argument is used as prefix for the .h and .c files generated.+compileWith :: CSettings -> String -> Spec -> IO ()+compileWith cSettings prefix spec+ | null triggers+ = do hPutStrLn stderr $+ "Copilot error: attempt at compiling empty specification.\n"+ ++ "You must define at least one trigger to generate C monitors."+ exitFailure++ | incompatibleTriggers triggers+ = do hPutStrLn stderr $+ "Copilot error: attempt at compiling specification with conflicting "+ ++ "trigger definitions.\n"+ ++ "Multiple triggers have the same name, but different argument "+ ++ "types.\n"+ exitFailure++ | otherwise+ = do let cFile = render $ pretty $ C.translate $ compileC cSettings spec+ hFile = render $ pretty $ C.translate $ compileH cSettings spec+ typeDeclnsFile = safeCRender $ compileTypeDeclns cSettings spec++ cMacros = unlines [ "#include <stdint.h>"+ , "#include <stdbool.h>"+ , "#include <string.h>"+ , "#include <stdlib.h>"+ , "#include <math.h>"+ , ""+ , "#include \"" ++ prefix ++ "_types.h\""+ , "#include \"" ++ prefix ++ ".h\""+ , ""+ ]++ let dir = cSettingsOutputDirectory cSettings+ createDirectoryIfMissing True dir+ writeFile (dir </> prefix ++ ".c") $ cMacros ++ cFile+ writeFile (dir </> prefix ++ ".h") hFile+ writeFile (dir </> prefix ++ "_types.h") typeDeclnsFile+ where+ triggers = specTriggers spec++ -- Check that two triggers do no conflict, that is: if their names are+ -- equal, the types of their arguments should be equal as well.+ incompatibleTriggers :: [Trigger] -> Bool+ incompatibleTriggers = pairwiseAny conflict+ where+ conflict :: Trigger -> Trigger -> Bool+ conflict t1@(Trigger name1 _ _) t2@(Trigger name2 _ _) =+ name1 == name2 && not (compareTrigger t1 t2)++ -- True if the function holds for any pair of elements. We assume that+ -- the function is commutative.+ pairwiseAny :: (a -> a -> Bool) -> [a] -> Bool+ pairwiseAny _ [] = False+ pairwiseAny _ (_:[]) = False+ pairwiseAny f (x:xs) = any (f x) xs || pairwiseAny f xs++-- | Compile a specification to a .h and a .c file.+--+-- The first argument is used as prefix for the .h and .c files generated.+compile :: String -> Spec -> IO ()+compile = compileWith mkDefaultCSettings++-- | Generate the .c file from a 'Spec'.+--+-- The generated C file has the following structure:+--+-- * Include .h file.+-- * Declarations of global buffers and indices.+-- * Generator functions for streams, guards and trigger arguments.+-- * Declaration of the @step()@ function.+compileC :: CSettings -> Spec -> C.TransUnit+compileC cSettings spec = C.TransUnit declns funs+ where+ declns = mkExts exts+ ++ mkGlobals streams++ funs = mkGenFuns streams uniqueTriggers+ ++ [mkStep cSettings streams uniqueTriggers exts]++ streams = specStreams spec+ triggers = specTriggers spec+ uniqueTriggers = mkUniqueTriggers triggers+ exts = gatherExts streams triggers++ -- Make declarations for copies of external variables.+ mkExts :: [External] -> [C.Decln]+ mkExts = map mkExtCpyDecln++ -- Make buffer and index declarations for streams.+ mkGlobals :: [Stream] -> [C.Decln]+ mkGlobals streamList = map buffDecln streamList+ ++ map indexDecln streamList+ where+ buffDecln (Stream sId buff _ ty) = mkBuffDecln sId ty buff+ indexDecln (Stream sId _ _ _ ) = mkIndexDecln sId++ -- Make generator functions, including trigger arguments.+ mkGenFuns :: [Stream] -> [UniqueTrigger] -> [C.FunDef]+ mkGenFuns streamList triggerList = map accessDecln streamList+ ++ map streamGen streamList+ ++ concatMap triggerGen triggerList+ where+ accessDecln :: Stream -> C.FunDef+ accessDecln (Stream sId buff _ ty) = mkAccessDecln sId ty buff++ streamGen :: Stream -> C.FunDef+ streamGen (Stream sId _ expr ty) =+ exprGen (generatorName sId) (generatorOutputArgName sId) expr ty++ triggerGen :: UniqueTrigger -> [C.FunDef]+ triggerGen (UniqueTrigger uniqueName (Trigger _name guard args)) = guardDef : argDefs+ where+ guardDef = mkGenFun (guardName uniqueName) guard Bool+ argDefs = zipWith argGen (argNames uniqueName) args++ argGen :: String -> UExpr -> C.FunDef+ argGen argName (UExpr ty expr) =+ exprGen argName (argName ++ "_output") expr ty++ -- Create a function that calculates the current value generated by an+ -- expression `expr` of type `ty`. The generator treats arrays+ -- specially, and the function takes an output array as a parameter.+ -- The second identifier `outputArrName` is not used if `expr` is not an+ -- array.+ exprGen :: C.Ident -> C.Ident -> Expr a -> Type a -> C.FunDef+ exprGen funName outputArrName expr ty@(Array _) =+ mkGenFunArray funName outputArrName expr ty+ exprGen funName _ expr ty =+ mkGenFun funName expr ty++-- | Generate the .h file from a 'Spec'.+compileH :: CSettings -> Spec -> C.TransUnit+compileH cSettings spec = C.TransUnit declns []+ where+ declns = mkStructForwDeclns exprs+ ++ mkExts exts+ ++ extFunDeclns triggers+ ++ [stepDecln]++ exprs = gatherExprs streams triggers+ exts = gatherExts streams triggers+ streams = specStreams spec++ -- Remove duplicates due to multiple guards for the same trigger.+ triggers = nubBy compareTrigger (specTriggers spec)++ mkStructForwDeclns :: [UExpr] -> [C.Decln]+ mkStructForwDeclns es = mapMaybe mkDecln uTypes+ where+ mkDecln (UType ty) = case ty of+ Struct _ -> Just $ mkStructForwDecln ty+ _ -> Nothing++ uTypes = nub $ concatMap (\(UExpr _ e) -> exprTypes e) es++ -- Make declarations for external variables.+ mkExts :: [External] -> [C.Decln]+ mkExts = map mkExtDecln++ extFunDeclns :: [Trigger] -> [C.Decln]+ extFunDeclns = map extFunDecln+ where+ extFunDecln :: Trigger -> C.Decln+ extFunDecln (Trigger name _ args) = C.FunDecln Nothing cTy name params+ where+ cTy = C.TypeSpec C.Void+ params = zipWith mkParam (argNames name) args++ mkParam paramName (UExpr ty _) = C.Param (mkParamTy ty) paramName++ -- Special case for Struct, to pass struct arguments by reference.+ -- Arrays are also passed by reference, but using C's array type+ -- does that automatically.+ mkParamTy ty =+ case ty of+ Struct _ -> C.Ptr (transType ty)+ _ -> transType ty++ -- Declaration for the step function.+ stepDecln :: C.Decln+ stepDecln = C.FunDecln Nothing (C.TypeSpec C.Void)+ (cSettingsStepFunctionName cSettings) []++-- | Generate a C translation unit that contains all type declarations needed+-- by the Copilot specification.+compileTypeDeclns :: CSettings -> Spec -> C.TransUnit+compileTypeDeclns _cSettings spec = C.TransUnit declns []+ where+ declns = mkTypeDeclns exprs++ exprs = gatherExprs streams triggers+ streams = specStreams spec+ triggers = specTriggers spec++ -- Generate type declarations.+ mkTypeDeclns :: [UExpr] -> [C.Decln]+ mkTypeDeclns es = mapMaybe mkTypeDecln uTypes+ where+ uTypes = nub $ concatMap (\(UExpr _ e) -> exprTypes e) es++ mkTypeDecln (UType ty) = case ty of+ Struct _ -> Just $ mkStructDecln ty+ _ -> Nothing++-- * Auxiliary definitions++-- | Render a C.TransUnit to a String, accounting for the case in which the+-- translation unit is empty.+safeCRender :: C.TransUnit -> String+safeCRender (C.TransUnit [] []) = ""+safeCRender transUnit = render $ pretty $ C.translate transUnit++-- ** Obtain information from Copilot Core Exprs and Types.++-- | List all types of an expression, returns items uniquely.+exprTypes :: Typeable a => Expr a -> [UType]+exprTypes e = case e of+ Const ty _ -> typeTypes ty+ Local ty1 ty2 _ e1 e2 -> typeTypes ty1 `union` typeTypes ty2+ `union` exprTypes e1 `union` exprTypes e2+ Var ty _ -> typeTypes ty+ Drop ty _ _ -> typeTypes ty+ ExternVar ty _ _ -> typeTypes ty+ Op1 _ e1 -> exprTypes e1+ Op2 _ e1 e2 -> exprTypes e1 `union` exprTypes e2+ Op3 _ e1 e2 e3 -> exprTypes e1 `union` exprTypes e2+ `union` exprTypes e3+ Label ty _ _ -> typeTypes ty++-- | List all types of a type, returns items uniquely.+typeTypes :: Typeable a => Type a -> [UType]+typeTypes ty = case ty of+ Array ty' -> typeTypes ty' `union` [UType ty]+ Struct x -> concatMap (\(Value ty' _) -> typeTypes ty') (toValues x)+ `union` [UType ty]+ _ -> [UType ty]++-- | Collect all expression of a list of streams and triggers and wrap them+-- into an UEXpr.+gatherExprs :: [Stream] -> [Trigger] -> [UExpr]+gatherExprs streams triggers = map streamUExpr streams+ ++ concatMap triggerUExpr triggers+ where+ streamUExpr (Stream _ _ expr ty) = UExpr ty expr+ triggerUExpr (Trigger _ guard args) = UExpr Bool guard : args++-- | We consider triggers to be equal, if their names match and the number and+-- types of arguments.+compareTrigger :: Trigger -> Trigger -> Bool+compareTrigger (Trigger name1 _ args1) (Trigger name2 _ args2)+ = name1 == name2 && compareArguments args1 args2++ where+ compareArguments :: [UExpr] -> [UExpr] -> Bool+ compareArguments [] [] = True+ compareArguments [] _ = False+ compareArguments _ [] = False+ compareArguments (x:xs) (y:ys) = compareUExpr x y && compareArguments xs ys++ compareUExpr :: UExpr -> UExpr -> Bool+ compareUExpr (UExpr ty1 _) (UExpr ty2 _)+ | Just Refl <- testEquality ty1 ty2 = True+ | otherwise = False
− src/Copilot/Compile/C99/Compile/Internal.hs
@@ -1,159 +0,0 @@--- | Compile Copilot specifications to C99 code.-module Copilot.Compile.C99.Compile.Internal- ( compile- , compileWith- ) where--import Text.PrettyPrint (render)-import Data.List (nub)-import Data.Maybe (catMaybes)-import System.Directory (createDirectoryIfMissing)-import System.Exit (exitFailure)-import System.FilePath ((</>))-import System.IO (hPutStrLn, stderr)--import Language.C99.Pretty (pretty)-import qualified Language.C99.Simple as C--import Copilot.Core-import Copilot.Compile.C99.Util-import Copilot.Compile.C99.External-import Copilot.Compile.C99.Settings.Internal-import Copilot.Compile.C99.Translate-import Copilot.Compile.C99.CodeGen---- | Compile a specification to a .h and a .c file.------ The first argument is the settings for the C code generated.------ The second argument is used as prefix for the .h and .c files generated.-compileWith :: CSettings -> String -> Spec -> IO ()-compileWith cSettings prefix spec- | null (specTriggers spec)- = do hPutStrLn stderr $- "Copilot error: attempt at compiling empty specification.\n"- ++ "You must define at least one trigger to generate C monitors."- exitFailure-- | otherwise- = do let cfile = render $ pretty $ C.translate $ compilec cSettings spec- hfile = render $ pretty $ C.translate $ compileh cSettings spec-- -- TODO: find a nicer solution using annotated AST's- -- Should figure out exactly which headers are needed, based on what- -- is used.- cmacros = unlines [ "#include <stdint.h>"- , "#include <stdbool.h>"- , "#include <string.h>"- , "#include <stdlib.h>"- , "#include <math.h>"- , ""- , "#include \"" ++ prefix ++ ".h\""- , ""- ]-- let dir = cSettingsOutputDirectory cSettings- createDirectoryIfMissing True dir- writeFile (dir </> prefix ++ ".c") $ cmacros ++ cfile- writeFile (dir </> prefix ++ ".h") hfile---- | Compile a specification to a .h and a .c file.------ The first argument is used as prefix for the .h and .c files generated.-compile :: String -> Spec -> IO ()-compile = compileWith mkDefaultCSettings---- | Generate the .c file from a 'Spec'.------ The generated C file has the following structure:------ * Include .h file.--- * Declarations of global buffers and indices.--- * Generator functions for streams, guards and trigger arguments.--- * Declaration of the @step()@ function.-compilec :: CSettings -> Spec -> C.TransUnit-compilec cSettings spec = C.TransUnit declns funs where- streams = specStreams spec- triggers = specTriggers spec- exts = gatherexts streams triggers- exprs = gatherexprs streams triggers-- declns = mkstructdeclns exprs ++ mkexts exts ++ mkglobals streams- funs = genfuns streams triggers ++ [mkstep cSettings streams triggers exts]-- -- Write struct datatypes- mkstructdeclns :: [UExpr] -> [C.Decln]- mkstructdeclns es = catMaybes $ map mkdecln utypes where- mkdecln (UType ty) = case ty of- Struct x -> Just $ mkstructdecln ty- _ -> Nothing-- utypes = nub $ concatMap (\(UExpr _ e) -> exprtypes e) es-- -- Make declarations for copies of external variables.- mkexts :: [External] -> [C.Decln]- mkexts exts = map mkextcpydecln exts-- -- Make buffer and index declarations for streams.- mkglobals :: [Stream] -> [C.Decln]- mkglobals streams = map buffdecln streams ++ map indexdecln streams where- buffdecln (Stream sid buff _ ty) = mkbuffdecln sid ty buff- indexdecln (Stream sid _ _ _ ) = mkindexdecln sid-- -- Make generator functions, including trigger arguments.- genfuns :: [Stream] -> [Trigger] -> [C.FunDef]- genfuns streams triggers = map accessdecln streams- ++ map streamgen streams- ++ concatMap triggergen triggers where-- accessdecln :: Stream -> C.FunDef- accessdecln (Stream sid buff _ ty) = mkaccessdecln sid ty buff-- streamgen :: Stream -> C.FunDef- streamgen (Stream sid _ expr ty) = genfun (generatorname sid) expr ty-- triggergen :: Trigger -> [C.FunDef]- triggergen (Trigger name guard args) = guarddef : argdefs where- guarddef = genfun (guardname name) guard Bool- argdefs = map arggen (zip (argnames name) args)-- arggen :: (String, UExpr) -> C.FunDef- arggen (argname, UExpr ty expr) = genfun argname expr ty---- | Generate the .h file from a 'Spec'.-compileh :: CSettings -> Spec -> C.TransUnit-compileh cSettings spec = C.TransUnit declns [] where- streams = specStreams spec- triggers = specTriggers spec- exts = gatherexts streams triggers- exprs = gatherexprs streams triggers-- declns = mkstructforwdeclns exprs- ++ mkexts exts- ++ extfundeclns triggers- ++ [stepdecln]-- mkstructforwdeclns :: [UExpr] -> [C.Decln]- mkstructforwdeclns es = catMaybes $ map mkdecln utypes where- mkdecln (UType ty) = case ty of- Struct x -> Just $ mkstructforwdecln ty- _ -> Nothing-- utypes = nub $ concatMap (\(UExpr _ e) -> exprtypes e) es-- -- Make declarations for external variables.- mkexts :: [External] -> [C.Decln]- mkexts = map mkextdecln-- extfundeclns :: [Trigger] -> [C.Decln]- extfundeclns triggers = map extfundecln triggers where- extfundecln :: Trigger -> C.Decln- extfundecln (Trigger name _ args) = C.FunDecln Nothing cty name params where- cty = C.TypeSpec C.Void- params = map mkparam $ zip (argnames name) args- mkparam (name, UExpr ty _) = C.Param (transtype ty) name-- -- Declaration for the step function.- stepdecln :: C.Decln- stepdecln = C.FunDecln Nothing (C.TypeSpec C.Void)- (cSettingsStepFunctionName cSettings) []
+ src/Copilot/Compile/C99/Error.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE Safe #-}++-- |+-- Copyright: (c) 2011 National Institute of Aerospace / Galois, Inc.+--+-- Custom functions to report error messages to users.+module Copilot.Compile.C99.Error+ ( impossible+ , errorEmptyStruct+ , errorZeroLengthArray+ )+ where++-- | Report an error due to a bug in Copilot.+impossible :: String -- ^ Name of the function in which the error was detected.+ -> String -- ^ Name of the package in which the function is located.+ -> a+impossible function package =+ error $ "Impossible error in function "+ ++ function ++ ", in package " ++ package+ ++ ". Please file an issue at "+ ++ "https://github.com/Copilot-Language/copilot/issues"+ ++ " or email the maintainers at <ivan.perezdominguez@nasa.gov>"++-- | Report an error when attempting to compile a zero-length array to C99.+-- C99 does not support zero-length arrays, so Copilot cannot compile+-- specifications that use them.+errorZeroLengthArray :: a+errorZeroLengthArray =+ error "copilot-c99: Cannot compile zero-length arrays to C99.\n"++-- | Report an error when attempting to compile an empty struct to C99.+-- C99 does not support empty structs, so Copilot cannot compile+-- specifications that use them.+errorEmptyStruct :: a+errorEmptyStruct =+ error "copilot-c99: Cannot compile empty structs to C99.\n"
+ src/Copilot/Compile/C99/Expr.hs view
@@ -0,0 +1,490 @@+{-# LANGUAGE GADTs #-}++-- | Translate Copilot Core expressions and operators to C99.+module Copilot.Compile.C99.Expr+ ( transExpr+ , constArray+ )+ where++-- External imports+import Control.Monad.State ( State, get, modify )+import qualified Data.List.NonEmpty as NonEmpty+import qualified Language.C99.Simple as C++-- Internal imports: Copilot+import Copilot.Core ( Array, Expr (..), Field (..), Op1 (..), Op2 (..),+ Op3 (..), Type (..), Value (..), accessorName,+ arrayElems, toValues, typeLength, typeSize )++-- Internal imports+import Copilot.Compile.C99.Error ( impossible, errorEmptyStruct, errorZeroLengthArray )+import Copilot.Compile.C99.Name ( exCpyName, streamAccessorName )+import Copilot.Compile.C99.Type ( transLocalVarDeclType, transType,+ transTypeName )++-- | Translates a Copilot Core expression into a C99 expression.+transExpr :: Expr a -> State FunEnv C.Expr+transExpr (Const ty x) = return $ constTy ty x++transExpr (Local ty1 _ name e1 e2) = do+ e1' <- transExpr e1+ let cTy1 = transLocalVarDeclType ty1+ initExpr = Just $ C.InitExpr e1'++ -- Add new decl to the tail of the fun env+ modify (\(i, x, y)+ -> (i, x ++ [C.VarDecln Nothing cTy1 name initExpr], y)+ )++ transExpr e2++transExpr (Var _ n) = return $ C.Ident n++transExpr (Drop _ amount sId) = do+ let accessVar = streamAccessorName sId+ index = C.LitInt (fromIntegral amount)+ return $ funCall accessVar [index]++transExpr (ExternVar _ name _) = return $ C.Ident (exCpyName name)++transExpr (Label _ _ e) = transExpr e -- ignore label++transExpr (Op1 op e) = do+ e' <- transExpr e+ return $ transOp1 op e'++transExpr (Op2 (UpdateField ty1@(Struct _) ty2 f) e1 e2) = do+ -- Translating a struct update Op requires initializing a variable to the+ -- "old" value, updating the field, and returning the new value.+ e1' <- transExpr e1+ e2' <- transExpr e2++ -- Variable to hold the updated struct+ (i, _, _) <- get+ let varName = "_v" ++ show i+ modify (\(i, x, y) -> (i + 1, x, y))++ -- Add new var decl+ let initDecl = C.VarDecln Nothing cTy1 varName Nothing+ cTy1 = transLocalVarDeclType ty1+ modify (\(i, x, y) -> (i, x ++ [initDecl], y))++ -- Initialize the var to the same value as the original struct+ let initStmt = C.Expr+ $ C.AssignOp+ C.Assign+ (C.Ident varName)+ e1'++ -- Update field f with given value e2.+ let updateStmt = case ty2 of+ Array _ -> C.Expr $ memcpy dest e2' size+ where+ dest = C.Dot (C.Ident varName) (accessorName f)+ size = C.LitInt+ (fromIntegral $ typeSize ty2)+ C..* C.SizeOfType (C.TypeName (tyElemName ty2))++ _ -> C.Expr+ $ C.AssignOp+ C.Assign+ (C.Dot (C.Ident varName) (accessorName f))+ e2'++ modify (\(i, x, y) -> (i, x, y ++ [ initStmt, updateStmt ]))++ return $ C.Ident varName++transExpr (Op2 op e1 e2) = do+ e1' <- transExpr e1+ e2' <- transExpr e2+ return $ transOp2 op e1' e2'++transExpr e@(Op3 (UpdateArray arrTy@(Array ty2)) e1 e2 e3) = do+ e1' <- transExpr e1+ e2' <- transExpr e2+ e3' <- transExpr e3++ -- Variable to hold the updated array+ (i, _, _) <- get+ let varName = "_v" ++ show i+ modify (\(i, x, y) -> (i + 1, x, y))++ -- Add new var decl+ let initDecl = C.VarDecln Nothing cTy1 varName Nothing+ cTy1 = transType arrTy+ modify (\(i, x, y) -> (i, x ++ [initDecl], y))++ let size :: Type (Array n t) -> C.Expr+ size arrTy@(Array ty) = C.LitInt (fromIntegral $ typeLength arrTy)+ C..* C.SizeOfType (C.TypeName $ transType ty)++ -- Initialize the var to the same value as the original array+ let initStmt = C.Expr $ memcpy (C.Ident varName) e1' (size arrTy)++ -- Update element of array+ let updateStmt = case ty2 of+ Array _ -> C.Expr $ memcpy dest e3' size+ where+ dest = C.Index (C.Ident varName) e2'+ size = C.LitInt+ (fromIntegral $ typeSize ty2)+ C..* C.SizeOfType (C.TypeName (tyElemName ty2))++ _ -> C.Expr+ $ C.AssignOp+ C.Assign+ (C.Index (C.Ident varName) e2')+ e3'++ modify (\(i, x, y) -> (i, x, y ++ [ initStmt, updateStmt ]))++ return $ C.Ident varName++transExpr (Op3 op e1 e2 e3) = do+ e1' <- transExpr e1+ e2' <- transExpr e2+ e3' <- transExpr e3+ return $ transOp3 op e1' e2' e3'++-- | Translates a Copilot unary operator and its argument into a C99+-- expression.+transOp1 :: Op1 a b -> C.Expr -> C.Expr+transOp1 op e =+ -- There are three types of ways in which a function in Copilot Core can be+ -- translated into C:+ --+ -- 1) Direct translation (perfect 1-to-1 mapping)+ -- 2) Type-directed translation (1-to-many mapping, choice based on type)+ -- 3) Desugaring/complex (expands to complex expression)+ case op of+ Not -> (C..!) e+ Abs ty -> transAbs ty e+ Sign ty -> transSign ty e+ Recip ty -> constNumTy ty 1 C../ e+ Acos ty -> funCall (specializeMathFunName ty "acos") [e]+ Asin ty -> funCall (specializeMathFunName ty "asin") [e]+ Atan ty -> funCall (specializeMathFunName ty "atan") [e]+ Cos ty -> funCall (specializeMathFunName ty "cos") [e]+ Sin ty -> funCall (specializeMathFunName ty "sin") [e]+ Tan ty -> funCall (specializeMathFunName ty "tan") [e]+ Acosh ty -> funCall (specializeMathFunName ty "acosh") [e]+ Asinh ty -> funCall (specializeMathFunName ty "asinh") [e]+ Atanh ty -> funCall (specializeMathFunName ty "atanh") [e]+ Cosh ty -> funCall (specializeMathFunName ty "cosh") [e]+ Sinh ty -> funCall (specializeMathFunName ty "sinh") [e]+ Tanh ty -> funCall (specializeMathFunName ty "tanh") [e]+ Exp ty -> funCall (specializeMathFunName ty "exp") [e]+ Log ty -> funCall (specializeMathFunName ty "log") [e]+ Sqrt ty -> funCall (specializeMathFunName ty "sqrt") [e]+ Ceiling ty -> funCall (specializeMathFunName ty "ceil") [e]+ Floor ty -> funCall (specializeMathFunName ty "floor") [e]+ BwNot _ -> (C..~) e+ Cast _ ty -> C.Cast (transTypeName ty) e+ GetField (Struct _) _ f -> C.Dot e (accessorName f)++-- | Translates a Copilot binary operator and its arguments into a C99+-- expression.+--+-- PRE: op is not a struct update operation (i.e., 'UpdateField').+transOp2 :: Op2 a b c -> C.Expr -> C.Expr -> C.Expr+transOp2 op e1 e2 = case op of+ And -> e1 C..&& e2+ Or -> e1 C..|| e2+ Add _ -> e1 C..+ e2+ Sub _ -> e1 C..- e2+ Mul _ -> e1 C..* e2+ Mod _ -> e1 C..% e2+ Div _ -> e1 C../ e2+ Fdiv _ -> e1 C../ e2+ Pow ty -> funCall (specializeMathFunName ty "pow") [e1, e2]+ Logb ty -> funCall (specializeMathFunName ty "log") [e2] C../+ funCall (specializeMathFunName ty "log") [e1]+ Atan2 ty -> funCall (specializeMathFunName ty "atan2") [e1, e2]+ Eq _ -> e1 C..== e2+ Ne _ -> e1 C..!= e2+ Le _ -> e1 C..<= e2+ Ge _ -> e1 C..>= e2+ Lt _ -> e1 C..< e2+ Gt _ -> e1 C..> e2+ BwAnd _ -> e1 C..& e2+ BwOr _ -> e1 C..| e2+ BwXor _ -> e1 C..^ e2+ BwShiftL _ _ -> e1 C..<< e2+ BwShiftR _ _ -> e1 C..>> e2+ Index _ -> C.Index e1 e2+ UpdateField _ _ _ -> impossible "transOp2" "copilot-c99"++-- | Translates a Copilot ternary operator and its arguments into a C99+-- expression.+transOp3 :: Op3 a b c d -> C.Expr -> C.Expr -> C.Expr -> C.Expr+transOp3 op e1 e2 e3 = case op of+ Mux _ -> C.Cond e1 e2 e3+ UpdateArray _ -> impossible "transOp3" "copilot-c99"++-- | Translate @'Abs' e@ in Copilot Core into a C99 expression.+--+-- This function produces a portable implementation of abs in C99 that works+-- for the type given, provided that the output fits in a variable of the same+-- type (which may not be true, for example, for signed integers in the lower+-- end of their type range). If the absolute value is out of range, the+-- behavior is undefined.+--+-- PRE: The type given is a Num type (floating-point number, or a+-- signed/unsigned integer of fixed size).+transAbs :: Type a -> C.Expr -> C.Expr+transAbs ty e+ -- Abs for floats/doubles is called fabs in C99's math.h.+ | typeIsFloating ty+ = funCall (specializeMathFunName ty "fabs") [e]++ -- C99 provides multiple implementations of abs, depending on the type of+ -- the arguments. For integers, it provides C99 abs, labs, and llabs, which+ -- take, respectively, an int, a long int, and a long long int.+ --+ -- However, the code produced by Copilot uses types with fixed width (e.g.,+ -- int16_t), and there is no guarantee that, for example, 32-bit int or+ -- 64-bit int will fit in a C int (only guaranteed to be 16 bits).+ -- Consequently, this function provides a portable version of abs for signed+ -- and unsigned ints implemented using shift and xor. For example, for a+ -- value x of type int32_t, the absolute value is:+ -- (x + (x >> sizeof(int32_t)-1)) ^ (x >> sizeof(int32_t)-1))+ | otherwise+ = (e C..+ (e C..>> tyBitSizeMinus1)) C..^ (e C..>> tyBitSizeMinus1)+ where+ -- Size of an integer type in bits, minus one. It's easier to hard-code+ -- them than to try and generate the right expressions in C using sizeof.+ --+ -- PRE: the type 'ty' is a signed or unsigned integer type.+ tyBitSizeMinus1 :: C.Expr+ tyBitSizeMinus1 = case ty of+ Int8 -> C.LitInt 7+ Int16 -> C.LitInt 15+ Int32 -> C.LitInt 31+ Int64 -> C.LitInt 63+ Word8 -> C.LitInt 7+ Word16 -> C.LitInt 15+ Word32 -> C.LitInt 31+ Word64 -> C.LitInt 63+ _ -> impossible+ "transAbs"+ "copilot-c99"+ "Abs applied to unexpected types."++-- | Translate @'Sign' e@ in Copilot Core into a C99 expression.+--+-- Sign is is translated as @e > 0 ? 1 : (e < 0 ? -1 : e)@, that is:+--+-- 1. If @e@ is positive, return @1@.+--+-- 2. If @e@ is negative, return @-1@.+--+-- 3. Otherwise, return @e@. This handles the case where @e@ is @0@ when the+-- type is an integral type. If the type is a floating-point type, it also+-- handles the cases where @e@ is @-0@ or @NaN@.+--+-- This implementation is modeled after how GHC implements 'signum'+-- <https://gitlab.haskell.org/ghc/ghc/-/blob/aed98ddaf72cc38fb570d8415cac5de9d8888818/libraries/base/GHC/Float.hs#L523-L525 here>.+transSign :: Type a -> C.Expr -> C.Expr+transSign ty e = positiveCase $ negativeCase e+ where+ -- If @e@ is positive, return @1@, otherwise fall back to argument.+ --+ -- Produces the following code, where @<arg>@ is the argument to this+ -- function:+ -- @+ -- e > 0 ? 1 : <arg>+ -- @+ positiveCase :: C.Expr -- ^ Value returned if @e@ is not positive.+ -> C.Expr+ positiveCase =+ C.Cond (C.BinaryOp C.GT e (constNumTy ty 0)) (constNumTy ty 1)++ -- If @e@ is negative, return @1@, otherwise fall back to argument.+ --+ -- Produces the following code, where @<arg>@ is the argument to this+ -- function:+ -- @+ -- e < 0 ? -1 : <arg>+ -- @+ negativeCase :: C.Expr -- ^ Value returned if @e@ is not negative.+ -> C.Expr+ negativeCase =+ C.Cond (C.BinaryOp C.LT e (constNumTy ty 0)) (constNumTy ty (-1))++-- | Transform a Copilot Core literal, based on its value and type, into a C99+-- literal.+constTy :: Type a -> a -> C.Expr+constTy ty = case ty of+ Bool -> C.LitBool+ Int8 -> explicitTy ty . C.LitInt . fromIntegral+ Int16 -> explicitTy ty . C.LitInt . fromIntegral+ Int32 -> explicitTy ty . C.LitInt . fromIntegral+ Int64 -> explicitTy ty . C.LitInt . fromIntegral+ Word8 -> explicitTy ty . C.LitInt . fromIntegral+ Word16 -> explicitTy ty . C.LitInt . fromIntegral+ Word32 -> explicitTy ty . C.LitInt . fromIntegral+ Word64 -> explicitTy ty . C.LitInt . fromIntegral+ Float -> explicitTy ty . C.LitFloat+ Double -> explicitTy ty . C.LitDouble+ Struct _ -> C.InitVal (transTypeName ty) . constStruct . toValues+ Array ty' -> C.InitVal (transTypeName ty) . constArray ty' . arrayElems++-- | Transform a Copilot Core literal, based on its value and type, into a C99+-- initializer.+constInit :: Type a -> a -> C.Init+constInit ty val = case ty of+ -- We include two special cases for Struct and Array to avoid using constTy+ -- on them.+ --+ -- In the default case (i.e., InitExpr (constTy ty val)), constant+ -- initializations are explicitly cast. However, doing so 1) may result in+ -- incorrect values for arrays, and 2) will be considered a non-constant+ -- expression in the case of arrays and structs, and thus not allowed as the+ -- initialization value for a global variable.+ --+ -- In particular, wrt. (1), for example, the nested array:+ -- [[0, 1], [2, 3]] :: Array 2 (Array 2 Int32)+ --+ -- with explicit casts, will be initialized in C as:+ -- { (int32_t[2]){(int32_t)(0), (int32_t)(1)},+ -- (int32_t[2]){(int32_t)(2), (int32_t)(3)} }+ --+ -- Due to the additional (int32_t[2]) casts, a C compiler will interpret the+ -- whole expression as an array of two int32_t's (as opposed to a nested+ -- array). This can either lead to compile-time errors (if you're lucky) or+ -- incorrect runtime semantics (if you're unlucky).+ Array ty' -> C.InitList $ constArray ty' $ arrayElems val++ -- We use InitArray to initialize a struct because the syntax used for+ -- initializing arrays and structs is compatible. For instance, {1, 2} works+ -- both for initializing an int array of length 2 as well as a struct with+ -- two int fields, although the two expressions are conceptually different+ -- (structs can also be initialized as { .a = 1, .b = 2}.+ Struct _ -> C.InitList $ constStruct (toValues val)+ _ -> C.InitExpr $ constTy ty val++-- | Transform a Copilot Core struct field into a C99 initializer.+constFieldInit :: Value a -> C.InitItem+constFieldInit (Value ty (Field val)) = C.InitItem Nothing $ constInit ty val++-- | Transform a Copilot Struct, based on the struct fields, into a list of C99+-- initializer values.+--+-- Raises an error if the struct is empty, as C99 does not support structs with+-- no fields.+constStruct :: [Value a] -> NonEmpty.NonEmpty C.InitItem+constStruct val = case val of+ [] -> errorEmptyStruct+ _ -> NonEmpty.fromList $ map constFieldInit val++-- | Transform a Copilot Array, based on the element values and their type,+-- into a list of C99 initializer values.+--+-- Raises an error if the array is empty, as C99 does not support zero-length+-- arrays.+constArray :: Type a -> [a] -> NonEmpty.NonEmpty C.InitItem+constArray ty xs = case xs of+ [] -> errorZeroLengthArray+ _ -> NonEmpty.fromList $ map (C.InitItem Nothing . constInit ty) xs++-- | Explicitly cast a C99 value to a type.+explicitTy :: Type a -> C.Expr -> C.Expr+explicitTy ty = C.Cast (transTypeName ty)++-- Translate a literal number of type @ty@ into a C99 literal.+--+-- PRE: The type of PRE is numeric (integer or floating-point), that+-- is, not boolean, struct or array.+constNumTy :: Type a -> Integer -> C.Expr+constNumTy ty =+ case ty of+ Float -> C.LitFloat . fromInteger+ Double -> C.LitDouble . fromInteger+ _ -> C.LitInt++-- | Provide a specialized function name in C99 for a function given the type+-- of its arguments, and its "family" name.+--+-- C99 provides multiple variants of the same conceptual function, based on the+-- types. Depending on the function, common variants exist for signed/unsigned+-- arguments, long or short types, float or double. The C99 standard uses the+-- same mechanism to name most such functions: the default variant works for+-- double, and there are additional variants for float and long double. For+-- example, the sin function operates on double, while sinf operates on float,+-- and sinl operates on long double.+--+-- This function only knows how to provide specialized names for functions in+-- math.h that provide a default version for a double argument and vary for+-- floats. It won't change the function name given if the variation is based on+-- the return type, if the function is defined elsewhere, or for other types.+specializeMathFunName :: Type a -> String -> String+specializeMathFunName ty s+ -- The following function pattern matches based on the variants available+ -- for a specific function.+ --+ -- Do not assume that a function you need implemented follows the same+ -- standard as others: check whether it is present in the standard.+ | isMathFPArgs s+ , Float <- ty+ = s ++ "f"++ | otherwise+ = s+ where+ -- True if the function family name is part of math.h and follows the+ -- standard rule of providing multiple variants for floating point numbers+ -- based on the type of their arguments.+ --+ -- Note: nan is not in this list because the names of its variants are+ -- determined by the return type.+ --+ -- For details, see:+ -- "B.11 Mathematics <math.h>" in the C99 standard+ isMathFPArgs :: String -> Bool+ isMathFPArgs = flip elem+ [ "acos", "asin", "atan", "atan2", "cos", "sin"+ , "tan", "acosh", "asinh", "atanh", "cosh", "sinh"+ , "tanh", "exp", "exp2", "expm1", "frexp", "ilogb"+ , "ldexp", "log", "log10", "log1p", "log2", "logb"+ , "modf", "scalbn", "scalbln", "cbrt", "fabs", "hypot"+ , "pow", "sqrt", "erf", "erfc", "lgamma", "tgamma"+ , "ceil", "floor", "nearbyint", "rint", "lrint", "llrint"+ , "round", "lround", "llround", "trunc", "fmod", "remainder"+ , "remquo", "copysign", "nextafter", "nexttoward", "fdim"+ , "fmax", "fmin", "fma"+ ]++-- * Auxiliary functions++-- | True if the type given is a floating point number.+typeIsFloating :: Type a -> Bool+typeIsFloating Float = True+typeIsFloating Double = True+typeIsFloating _ = False++-- | Auxiliary type used to collect all the declarations of all the variables+-- used in a function to be generated, since variable declarations are always+-- listed first at the top of the function body.+type FunEnv = (Int, [C.Decln], [C.Stmt])++-- | Define a C expression that calls a function with arguments.+funCall :: C.Ident -- ^ Function name+ -> [C.Expr] -- ^ Arguments+ -> C.Expr+funCall name = C.Funcall (C.Ident name)++-- Write a call to the memcpy function.+memcpy :: C.Expr -> C.Expr -> C.Expr -> C.Expr+memcpy dest src size = C.Funcall (C.Ident "memcpy") [dest, src, size]++-- Translate a Copilot type to a C99 type, handling arrays especially.+--+-- If the given type is an array (including multi-dimensional arrays), the+-- type is that of the elements in the array. Otherwise, it is just the+-- equivalent representation of the given type in C.+tyElemName :: Type a -> C.Type+tyElemName ty = case ty of+ Array ty' -> tyElemName ty'+ _ -> transType ty
src/Copilot/Compile/C99/External.hs view
@@ -3,52 +3,60 @@ -- | Represent information about externs needed in the generation of C99 code -- for stream declarations and triggers. module Copilot.Compile.C99.External- {-# DEPRECATED "This module will be hidden in future versions." #-}+ ( External(..)+ , gatherExts+ ) where +-- External imports import Data.List (unionBy) -import Copilot.Core-import Copilot.Compile.C99.Util+-- Internal imports: Copilot+import Copilot.Core ( Expr (..), Stream (..), Trigger (..), Type, UExpr (..) ) +-- Internal imports+import Copilot.Compile.C99.Name ( exCpyName )+ -- | Representation of external variables. data External = forall a. External- { extname :: String- , extcpyname :: String- , exttype :: Type a+ { extName :: String+ , extCpyName :: String+ , extType :: Type a } --- | Union over lists of External, we solely base the equality on the--- extname's.-extunion :: [External] -> [External] -> [External]-extunion = unionBy (\a b -> extname a == extname b)- -- | Collect all external variables from the streams and triggers. -- -- Although Copilot specifications can contain also properties and theorems, -- the C99 backend currently only generates code for streams and triggers.-gatherexts :: [Stream] -> [Trigger] -> [External]-gatherexts streams triggers = streamsexts `extunion` triggersexts where- streamsexts = foldr extunion mempty $ map streamexts streams- triggersexts = foldr extunion mempty $ map triggerexts triggers+gatherExts :: [Stream] -> [Trigger] -> [External]+gatherExts streams triggers = streamsExts `extUnion` triggersExts+ where+ streamsExts = foldr (extUnion . streamExts) mempty streams+ triggersExts = foldr (extUnion . triggerExts) mempty triggers - streamexts :: Stream -> [External]- streamexts (Stream _ _ expr _) = exprexts expr+ streamExts :: Stream -> [External]+ streamExts (Stream _ _ expr _) = exprExts expr - triggerexts :: Trigger -> [External]- triggerexts (Trigger _ guard args) = guardexts `extunion` argexts where- guardexts = exprexts guard- argexts = concat $ map uexprexts args+ triggerExts :: Trigger -> [External]+ triggerExts (Trigger _ guard args) = guardExts `extUnion` argExts+ where+ guardExts = exprExts guard+ argExts = concatMap uExprExts args - uexprexts :: UExpr -> [External]- uexprexts (UExpr _ expr) = exprexts expr+ uExprExts :: UExpr -> [External]+ uExprExts (UExpr _ expr) = exprExts expr - exprexts :: Expr a -> [External]- exprexts expr = let rec = exprexts in case expr of- Local _ _ _ e1 e2 -> rec e1 `extunion` rec e2- ExternVar ty name _ -> [External name (excpyname name) ty]- Op1 _ e -> rec e- Op2 _ e1 e2 -> rec e1 `extunion` rec e2- Op3 _ e1 e2 e3 -> rec e1 `extunion` rec e2 `extunion` rec e3- Label _ _ e -> rec e- _ -> []+ exprExts :: Expr a -> [External]+ exprExts (Local _ _ _ e1 e2) = exprExts e1 `extUnion` exprExts e2+ exprExts (ExternVar ty name _) = [External name (exCpyName name) ty]+ exprExts (Op1 _ e) = exprExts e+ exprExts (Op2 _ e1 e2) = exprExts e1 `extUnion` exprExts e2+ exprExts (Op3 _ e1 e2 e3) = exprExts e1 `extUnion` exprExts e2+ `extUnion` exprExts e3+ exprExts (Label _ _ e) = exprExts e+ exprExts _ = []++ -- | Union over lists of External, we solely base the equality on the+ -- extName's.+ extUnion :: [External] -> [External] -> [External]+ extUnion = unionBy (\a b -> extName a == extName b)
+ src/Copilot/Compile/C99/Name.hs view
@@ -0,0 +1,61 @@+-- | Naming of variables and functions in C.+module Copilot.Compile.C99.Name+ ( argNames+ , argTempNames+ , exCpyName+ , generatorName+ , generatorOutputArgName+ , guardName+ , indexName+ , streamAccessorName+ , streamName+ )+ where++-- External imports: Copilot+import Copilot.Core (Id)++-- | Turn a stream id into a suitable C variable name.+streamName :: Id -> String+streamName sId = "s" ++ show sId++-- | Turn a stream id into the global varname for indices.+indexName :: Id -> String+indexName sId = streamName sId ++ "_idx"++-- | Turn a stream id into the name of its accessor function+streamAccessorName :: Id -> String+streamAccessorName sId = streamName sId ++ "_get"++-- | Add a postfix for copies of external variables the name.+exCpyName :: String -> String+exCpyName name = name ++ "_cpy"++-- | Turn stream id into name of its generator function.+generatorName :: Id -> String+generatorName sId = streamName sId ++ "_gen"++-- | Turn stream id into name of its output argument array.+generatorOutputArgName :: Id -> String+generatorOutputArgName sId = streamName sId ++ "_output"++-- | Turn the name of a trigger into a guard generator.+guardName :: String -> String+guardName name = name ++ "_guard"++-- | Turn a trigger name into a trigger argument name.+argName :: String -> Int -> String+argName name n = name ++ "_arg" ++ show n++-- | Turn a handler function name into a name for a temporary variable for a+-- handler argument.+argTempName :: String -> Int -> String+argTempName name n = name ++ "_arg_temp" ++ show n++-- | Enumerate all argument names based on trigger name.+argNames :: String -> [String]+argNames base = map (argName base) [0..]++-- | Enumerate all temporary variable names based on handler function name.+argTempNames :: String -> [String]+argTempNames base = map (argTempName base) [0..]
+ src/Copilot/Compile/C99/Representation.hs view
@@ -0,0 +1,21 @@+-- | C99 backend specific versions of selected `Copilot.Core` datatypes.+module Copilot.Compile.C99.Representation+ ( UniqueTrigger (..)+ , UniqueTriggerId+ , mkUniqueTriggers+ )+ where++import Copilot.Core ( Trigger (..) )++-- | Internal unique name for a trigger.+type UniqueTriggerId = String++-- | A `Copilot.Core.Trigger` with an unique name.+data UniqueTrigger = UniqueTrigger UniqueTriggerId Trigger++-- | Given a list of triggers, make their names unique.+mkUniqueTriggers :: [Trigger] -> [UniqueTrigger]+mkUniqueTriggers ts = zipWith mkUnique ts [0..]+ where+ mkUnique t@(Trigger name _ _) n = UniqueTrigger (name ++ "_" ++ show n) t
src/Copilot/Compile/C99/Settings.hs view
@@ -1,8 +1,16 @@ -- | Settings used by the code generator to customize the code. module Copilot.Compile.C99.Settings- {-# DEPRECATED "This module will be hidden in future versions." #-}- (module Copilot.Compile.C99.Settings.Internal)- where+ ( CSettings(..)+ , mkDefaultCSettings+ )+ where -import Copilot.Compile.C99.Settings.Internal+-- | Settings used to customize the code generated.+data CSettings = CSettings+ { cSettingsStepFunctionName :: String+ , cSettingsOutputDirectory :: FilePath+ } +-- | Default settings with a step function called @step@.+mkDefaultCSettings :: CSettings+mkDefaultCSettings = CSettings "step" "."
− src/Copilot/Compile/C99/Settings/Internal.hs
@@ -1,12 +0,0 @@--- | Settings used by the code generator to customize the code.-module Copilot.Compile.C99.Settings.Internal where---- | Settings used to customize the code generated.-data CSettings = CSettings- { cSettingsStepFunctionName :: String- , cSettingsOutputDirectory :: FilePath- }---- | Default settings with a step function called @step@.-mkDefaultCSettings :: CSettings-mkDefaultCSettings = CSettings "step" "."
− src/Copilot/Compile/C99/Translate.hs
@@ -1,169 +0,0 @@-{-# LANGUAGE GADTs #-}---- | Translate Copilot Core expressions and operators to C99.-module Copilot.Compile.C99.Translate- {-# DEPRECATED "This module will be hidden in future versions." #-}- where--import Control.Monad.State--import Copilot.Core-import Copilot.Compile.C99.Util--import qualified Language.C99.Simple as C---- | Translates a Copilot Core expression into a C99 expression.-transexpr :: Expr a -> State FunEnv C.Expr-transexpr (Const ty x) = return $ constty ty x--transexpr (Local ty1 _ name e1 e2) = do- e1' <- transexpr e1- let cty1 = transtype ty1- init = Just $ C.InitExpr e1'- statetell [C.VarDecln Nothing cty1 name init]-- transexpr e2--transexpr (Var _ n) = return $ C.Ident n--transexpr (Drop _ amount sid) = do- let accessvar = streamaccessorname sid- index = C.LitInt (fromIntegral amount)- return $ funcall accessvar [index]--transexpr (ExternVar _ name _) = return $ C.Ident (excpyname name)--transexpr (Label _ _ e) = transexpr e -- ignore label--transexpr (Op1 op e) = do- e' <- transexpr e- return $ transop1 op e'--transexpr (Op2 op e1 e2) = do- e1' <- transexpr e1- e2' <- transexpr e2- return $ transop2 op e1' e2'--transexpr (Op3 op e1 e2 e3) = do- e1' <- transexpr e1- e2' <- transexpr e2- e3' <- transexpr e3- return $ transop3 op e1' e2' e3'----- | Translates a Copilot unary operator and its argument into a C99--- expression.-transop1 :: Op1 a b -> C.Expr -> C.Expr-transop1 op e = case op of- Not -> (C..!) e- Abs _ -> funcall "abs" [e]- Sign _ -> funcall "copysign" [C.LitDouble 1.0, e]- Recip _ -> C.LitDouble 1.0 C../ e- Exp _ -> funcall "exp" [e]- Sqrt _ -> funcall "sqrt" [e]- Log _ -> funcall "log" [e]- Sin _ -> funcall "sin" [e]- Tan _ -> funcall "tan" [e]- Cos _ -> funcall "cos" [e]- Asin _ -> funcall "asin" [e]- Atan _ -> funcall "atan" [e]- Acos _ -> funcall "acos" [e]- Sinh _ -> funcall "sinh" [e]- Tanh _ -> funcall "tanh" [e]- Cosh _ -> funcall "cosh" [e]- Asinh _ -> funcall "asinh" [e]- Atanh _ -> funcall "atanh" [e]- Acosh _ -> funcall "acosh" [e]- Ceiling _ -> funcall "ceil" [e]- Floor _ -> funcall "floor" [e]- BwNot _ -> (C..~) e- Cast _ ty -> C.Cast (transtypename ty) e- GetField (Struct _) _ f -> C.Dot e (accessorname f)---- | Translates a Copilot binary operator and its arguments into a C99--- expression.-transop2 :: Op2 a b c -> C.Expr -> C.Expr -> C.Expr-transop2 op e1 e2 = case op of- And -> e1 C..&& e2- Or -> e1 C..|| e2- Add _ -> e1 C..+ e2- Sub _ -> e1 C..- e2- Mul _ -> e1 C..* e2- Mod _ -> e1 C..% e2- Div _ -> e1 C../ e2- Fdiv _ -> e1 C../ e2- Pow _ -> funcall "pow" [e1, e2]- Logb _ -> funcall "log" [e2] C../ funcall "log" [e1]- Atan2 _ -> funcall "atan2" [e1, e2]- Eq _ -> e1 C..== e2- Ne _ -> e1 C..!= e2- Le _ -> e1 C..<= e2- Ge _ -> e1 C..>= e2- Lt _ -> e1 C..< e2- Gt _ -> e1 C..> e2- BwAnd _ -> e1 C..& e2- BwOr _ -> e1 C..| e2- BwXor _ -> e1 C..^ e2- BwShiftL _ _ -> e1 C..<< e2- BwShiftR _ _ -> e1 C..>> e2- Index _ -> C.Index e1 e2---- | Translates a Copilot ternary operator and its arguments into a C99--- expression.-transop3 :: Op3 a b c d -> C.Expr -> C.Expr -> C.Expr -> C.Expr-transop3 op e1 e2 e3 = case op of- Mux _ -> C.Cond e1 e2 e3---- | Transform a Copilot Core literal, based on its value and type, into a C99--- literal.-constty :: Type a -> a -> C.Expr-constty ty = case ty of- Bool -> C.LitBool- Int8 -> explicitty ty . C.LitInt . fromIntegral- Int16 -> explicitty ty . C.LitInt . fromIntegral- Int32 -> explicitty ty . C.LitInt . fromIntegral- Int64 -> explicitty ty . C.LitInt . fromIntegral- Word8 -> explicitty ty . C.LitInt . fromIntegral- Word16 -> explicitty ty . C.LitInt . fromIntegral- Word32 -> explicitty ty . C.LitInt . fromIntegral- Word64 -> explicitty ty . C.LitInt . fromIntegral- Float -> explicitty ty . C.LitFloat- Double -> explicitty ty . C.LitDouble- Struct _ -> \v -> C.InitVal (transtypename ty) (map fieldinit (toValues v))- where- fieldinit (Value ty (Field val)) = C.InitExpr $ constty ty val- Array ty' -> \v -> C.InitVal (transtypename ty) (vals v)- where- vals v = constarray ty' (arrayelems v)-- constarray :: Type a -> [a] -> [C.Init]- constarray ty xs = case ty of- Array ty' -> constarray ty' (concatMap arrayelems xs)- _ -> map (C.InitExpr . constty ty) xs----- | Explicitly cast a C99 value to a type.-explicitty :: Type a -> C.Expr -> C.Expr-explicitty ty = C.Cast (transtypename ty)---- | Translate a Copilot type to a C99 type.-transtype :: Type a -> C.Type-transtype ty = case ty of- Bool -> C.TypeSpec $ C.TypedefName "bool"- Int8 -> C.TypeSpec $ C.TypedefName "int8_t"- Int16 -> C.TypeSpec $ C.TypedefName "int16_t"- Int32 -> C.TypeSpec $ C.TypedefName "int32_t"- Int64 -> C.TypeSpec $ C.TypedefName "int64_t"- Word8 -> C.TypeSpec $ C.TypedefName "uint8_t"- Word16 -> C.TypeSpec $ C.TypedefName "uint16_t"- Word32 -> C.TypeSpec $ C.TypedefName "uint32_t"- Word64 -> C.TypeSpec $ C.TypedefName "uint64_t"- Float -> C.TypeSpec C.Float- Double -> C.TypeSpec C.Double- Array ty' -> C.Array (transtype ty') length where- length = Just $ C.LitInt $ fromIntegral $ tylength ty- Struct s -> C.TypeSpec $ C.Struct (typename s)---- | Translate a Copilot type intro a C typename-transtypename :: Type a -> C.TypeName-transtypename ty = C.TypeName $ transtype ty
+ src/Copilot/Compile/C99/Type.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE GADTs #-}++-- | Translate Copilot Core expressions and operators to C99.+module Copilot.Compile.C99.Type+ ( transType+ , transLocalVarDeclType+ , transTypeName+ )+ where++-- External imports+import qualified Language.C99.Simple as C++-- Internal imports: Copilot+import Copilot.Core ( Struct (..), Type (..), typeLength,+ typeName )+import Copilot.Compile.C99.Error ( errorEmptyStruct, errorZeroLengthArray )++-- | Translate a Copilot type to a C99 type.+transType :: Type a -> C.Type+transType ty = case ty of+ Bool -> C.TypeSpec $ C.TypedefName "bool"+ Int8 -> C.TypeSpec $ C.TypedefName "int8_t"+ Int16 -> C.TypeSpec $ C.TypedefName "int16_t"+ Int32 -> C.TypeSpec $ C.TypedefName "int32_t"+ Int64 -> C.TypeSpec $ C.TypedefName "int64_t"+ Word8 -> C.TypeSpec $ C.TypedefName "uint8_t"+ Word16 -> C.TypeSpec $ C.TypedefName "uint16_t"+ Word32 -> C.TypeSpec $ C.TypedefName "uint32_t"+ Word64 -> C.TypeSpec $ C.TypedefName "uint64_t"+ Float -> C.TypeSpec C.Float+ Double -> C.TypeSpec C.Double+ Array ty' | typeLength ty == 0 -> errorZeroLengthArray+ | otherwise -> C.Array (transType ty') len+ where+ len = Just $ C.LitInt $ fromIntegral $ typeLength ty+ Struct s | null (toValues s) -> errorEmptyStruct+ | otherwise -> C.TypeSpec $ C.Struct (typeName s)++-- | Translate a Copilot type to a valid (local) variable declaration C99 type.+--+-- If the type denotes an array, translate it to a pointer to whatever the+-- array holds. This special case is needed when the type is used for a local+-- variable declaration. We treat global variables differently (we generate+-- list initializers).+transLocalVarDeclType :: Type a -> C.Type+transLocalVarDeclType (Array ty') = C.Ptr $ transType ty'+transLocalVarDeclType ty = transType ty++-- | Translate a Copilot type intro a C typename+transTypeName :: Type a -> C.TypeName+transTypeName ty = C.TypeName $ transType ty
− src/Copilot/Compile/C99/Util.hs
@@ -1,64 +0,0 @@--- | Auxiliary helper functions to generate C99 code.-module Copilot.Compile.C99.Util- {-# DEPRECATED "This module will be hidden in future versions." #-}- where--import Control.Monad.State--import Copilot.Core (Id)-import qualified Language.C99.Simple.AST as C---- | Auxiliary type used to collect all the declarations of all the variables--- used in a function to be generated, since variable declarations are always--- listed first at the top of the function body.-type FunEnv = [C.Decln]---- | `tell` equivalent for `State`.-statetell :: Monoid m => m -> State m ()-statetell m = modify ((flip mappend) m)---- | Generate fresh variable name based on a given one.-fresh :: String -> [String] -> String-fresh name used = head $ dropWhile (flip elem used) (name:freshnames) where- freshnames = (name ++).show <$> [0..]---- | Collect all the names from a list of C99 declarations.-names :: [C.Decln] -> [String]-names ds = map match ds where- match (C.VarDecln _ _ name _) = name---- | Turn a stream id into a suitable C variable name.-streamname :: Id -> String-streamname sid = "s" ++ show sid---- | Turn a stream id into the global varname for indices.-indexname :: Id -> String-indexname sid = streamname sid ++ "_idx"---- | Turn a stream id into the name of its accessor function-streamaccessorname :: Id -> String-streamaccessorname sid = streamname sid ++ "_get"---- | Add a postfix for copies of external variables the name.-excpyname :: String -> String-excpyname name = name ++ "_cpy"---- | Turn stream id into name of its generator function.-generatorname :: Id -> String-generatorname sid = streamname sid ++ "_gen"---- | Turn the name of a trigger into a guard generator.-guardname :: String -> String-guardname name = name ++ "_guard"---- | Turn a trigger name into a an trigger argument name.-argname :: String -> Int -> String-argname name n = name ++ "_arg" ++ show n---- | Enumerate all argument names based on trigger name.-argnames :: String -> [String]-argnames base = [aname | n <- [0..], let aname = argname base n]---- | Define a C expression that calls a function with arguments.-funcall :: C.Ident -> [C.Expr] -> C.Expr-funcall name args = C.Funcall (C.Ident name) args
+ tests/Main.hs view
@@ -0,0 +1,18 @@+-- | Test copilot-c99.+module Main where++-- External imports+import Test.Framework (Test, defaultMain)++-- Internal library modules being tested+import qualified Test.Copilot.Compile.C99++-- | Run all unit tests on copilot-c99.+main :: IO ()+main = defaultMain tests++-- | All unit tests in copilot-c99.+tests :: [Test.Framework.Test]+tests =+ [ Test.Copilot.Compile.C99.tests+ ]
+ tests/Test/Copilot/Compile/C99.hs view
@@ -0,0 +1,1042 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE ScopedTypeVariables #-}+-- | Test copilot-c99:Copilot.Compile.C99.+module Test.Copilot.Compile.C99+ ( tests )+ where++-- External imports+import Control.Arrow ((&&&))+import Control.Exception (ErrorCall (..), IOException, catch,+ try)+import Control.Monad (unless, when)+import Data.Bits (Bits, complement)+import Data.List (intercalate, isSubsequenceOf)+import Data.Type.Equality (testEquality)+import Data.Typeable (Proxy (..), (:~:) (Refl))+import GHC.TypeLits (KnownNat, natVal)+import System.Directory (doesFileExist,+ getTemporaryDirectory,+ removeDirectory, removeFile,+ setCurrentDirectory)+import System.IO (hPutStrLn, stderr)+import System.Posix.Temp (mkdtemp)+import System.Process (callProcess, readProcess)+import System.Random (Random)+import Test.Framework (Test, testGroup)+import Test.Framework.Providers.HUnit (testCase)+import Test.Framework.Providers.QuickCheck2 (testProperty)+import Test.HUnit (Assertion, assertFailure)+import Test.QuickCheck (Arbitrary, Gen, Property,+ arbitrary, choose, elements,+ forAll, forAllBlind, frequency,+ getPositive, ioProperty, oneof,+ vectorOf, (.&&.))+import Test.QuickCheck.Gen (chooseAny, chooseBoundedIntegral)++-- External imports: Copilot+import Copilot.Core hiding (Property)+import Copilot.Core.Type.Array (array)++-- External imports: Modules being tested+import Copilot.Compile.C99 (cSettingsOutputDirectory, compile, compileWith,+ mkDefaultCSettings)++-- * Constants++-- | All unit tests for copilot-core:Copilot.Core.Type.+tests :: Test.Framework.Test+tests =+ testGroup "Copilot.Compile.C99"+ [ testProperty "Compile specification" testCompile+ , testProperty "Compile specification in custom dir" testCompileCustomDir+ , testProperty "Run specification" testRun+ , testProperty "Run and compare results" testRunCompare+ , testCase "Reject empty arrays and structs" testRejectEmpties+ ]++-- * Individual tests++-- | Test compiling a spec.+testCompile :: Property+testCompile = ioProperty $ do+ tmpDir <- getTemporaryDirectory+ setCurrentDirectory tmpDir++ testDir <- mkdtemp "copilot_test_"+ setCurrentDirectory testDir++ compile "copilot_test" spec+ r <- compileC "copilot_test"++ -- Remove file produced by GCC+ removeFile "copilot_test.o"++ -- Remove files produced by Copilot+ removeFile "copilot_test.c"+ removeFile "copilot_test.h"+ removeFile "copilot_test_types.h"++ setCurrentDirectory tmpDir+ removeDirectory testDir++ return r++ where++ spec = Spec streams observers triggers properties++ streams = [ Stream 0 [1] (Const Int8 1) Int8]+ observers = []+ triggers = [ Trigger function guard args ]+ properties = []++ function = "func"++ guard = Const Bool True++ args = []++-- | Test compiling a spec in a custom directory.+testCompileCustomDir :: Property+testCompileCustomDir = ioProperty $ do+ tmpDir <- getTemporaryDirectory+ setCurrentDirectory tmpDir++ testDir <- mkdtemp "copilot_test_"++ compileWith (mkDefaultCSettings { cSettingsOutputDirectory = testDir })+ "copilot_test"+ spec++ setCurrentDirectory testDir+ r <- compileC "copilot_test"++ -- Remove file produced by GCC+ removeFile "copilot_test.o"++ -- Remove files produced by Copilot+ removeFile "copilot_test.c"+ removeFile "copilot_test.h"+ removeFile "copilot_test_types.h"++ setCurrentDirectory tmpDir+ removeDirectory testDir++ return r++ where++ spec = Spec streams observers triggers properties++ streams = [ Stream 0 [1] (Const Int8 1) Int8]+ observers = []+ triggers = [ Trigger function guard args ]+ properties = []++ function = "nop"++ guard = Const Bool True++ args = []++data Empty = Empty++instance Struct Empty where+ typeName _ = ""+ toValues _ = []++instance Typed Empty where+ typeOf = Struct Empty++-- | Test that compiling a spec with an empty array or struct raises an error.+-- C99 does not support zero-length arrays or structs without fields, so Copilot+-- should report an error during compilation.+testRejectEmpties :: Assertion+testRejectEmpties = do+ tmpDir <- getTemporaryDirectory+ setCurrentDirectory tmpDir++ r1 <- try (compile "copilot_test" $ spec t1) :: IO (Either ErrorCall ())+ unless (isErrZeroArray r1) $ assertFailure "Const zero-length array"++ r2 <- try (compile "copilot_test" $ spec t2) :: IO (Either ErrorCall ())+ unless (isErrZeroArray r2) $ assertFailure "Extern zero-length array"++ r3 <- try (compile "copilot_test" $ spec t3) :: IO (Either ErrorCall ())+ unless (isErrEmptyStruct r3) $ assertFailure "Const empty struct"++ r4 <- try (compile "copilot_test" $ spec t4) :: IO (Either ErrorCall ())+ unless (isErrEmptyStruct r4) $ assertFailure "Extern empty struct"++ where++ spec a = Spec streams observers (triggers a) properties++ streams = []+ observers = []+ triggers a = [ Trigger "trig" (Const Bool True) a ]+ properties = []++ t1 = [ UExpr arrTy (Const arrTy (array [])) ]+ t2 = [ UExpr arrTy (ExternVar arrTy "arrs" Nothing) ]+ t3 = [ UExpr structTy (Const structTy Empty) ]+ t4 = [ UExpr structTy (ExternVar structTy "structs" Nothing) ]++ arrTy :: Type (Array 0 Bool)+ arrTy = Array Bool++ structTy :: Type Empty+ structTy = Struct Empty++ isErrZeroArray :: Either ErrorCall a -> Bool+ isErrZeroArray e = case e of+ Left (ErrorCall msg) -> "zero-length array" `isSubsequenceOf` msg+ _ -> False++ isErrEmptyStruct :: Either ErrorCall a -> Bool+ isErrEmptyStruct e = case e of+ Left (ErrorCall msg) -> "empty struct" `isSubsequenceOf` msg+ _ -> False++-- | Test compiling a spec and running the resulting program.+--+-- The actual behavior is ignored.+testRun :: Property+testRun = ioProperty $ do+ tmpDir <- getTemporaryDirectory+ setCurrentDirectory tmpDir++ testDir <- mkdtemp "copilot_test_"+ setCurrentDirectory testDir++ compile "copilot_test" spec+ r <- compileC "copilot_test"++ let cProgram = unlines+ [ "#include \"copilot_test.h\""+ , ""+ , "void nop () {"+ , "}"+ , ""+ , "int main () {"+ , " step();"+ , "}"+ ]++ writeFile "main.c" cProgram++ -- Compile a main program+ r2 <- compileExecutable "main" [ "copilot_test.o" ]+ callProcess "./main" []++ -- Remove file produced by GCC+ removeFile "copilot_test.o"+ removeFile "main"++ -- Remove files produced "by hand"+ removeFile "main.c"++ -- Remove files produced by Copilot+ removeFile "copilot_test.c"+ removeFile "copilot_test.h"+ removeFile "copilot_test_types.h"++ setCurrentDirectory tmpDir+ removeDirectory testDir++ return $ r && r2++ where++ spec = Spec streams observers triggers properties++ streams = [ Stream 0 [1] (Const Int8 1) Int8]+ observers = []+ triggers = [ Trigger function guard args ]+ properties = []++ function = "nop"++ guard = Const Bool True++ args = []++-- | Test running compiled spec and comparing the results to the+-- expectation.+testRunCompare :: Property+testRunCompare =+ testRunCompare1 (arbitraryOpIntegralBool :: Gen (TestCase1 Int8 Bool))+ .&&. testRunCompare1 (arbitraryOpIntegralBool :: Gen (TestCase1 Int16 Bool))+ .&&. testRunCompare1 (arbitraryOpIntegralBool :: Gen (TestCase1 Int32 Bool))+ .&&. testRunCompare1 (arbitraryOpIntegralBool :: Gen (TestCase1 Int64 Bool))+ .&&. testRunCompare1 (arbitraryOpIntegralBool :: Gen (TestCase1 Word8 Bool))+ .&&. testRunCompare1 (arbitraryOpIntegralBool :: Gen (TestCase1 Word16 Bool))+ .&&. testRunCompare1 (arbitraryOpIntegralBool :: Gen (TestCase1 Word32 Bool))+ .&&. testRunCompare1 (arbitraryOpIntegralBool :: Gen (TestCase1 Word64 Bool))+ .&&. testRunCompare1 (arbitraryOpFloatingBool :: Gen (TestCase1 Float Bool))+ .&&. testRunCompare1 (arbitraryOpFloatingBool :: Gen (TestCase1 Double Bool))+ .&&. testRunCompare2 (arbitraryArrayNum :: Gen (TestCase2 (Array 2 Int8) Word32 Int8))+ .&&. testRunCompare2 (arbitraryArrayNum :: Gen (TestCase2 (Array 2 Int16) Word32 Int16))++-- * Random generators++-- ** Random function generators++-- | Generator of functions that produce booleans.+arbitraryOpBool :: Typed a => Gen (Fun a Bool, [a] -> [Bool])+arbitraryOpBool =+ frequency+ [ (5, arbitraryOp1Any)+ , (5, funCompose1 <$> arbitraryOp1Bool <*> arbitraryOpBool)+ , (2, funCompose2 <$> arbitraryOp2Bool <*> arbitraryOpBool <*> arbitraryOpBool)+ , (1, funCompose2 <$> arbitraryOp2Eq <*> arbitraryOpBool <*> arbitraryOpBool)+ , (1, funCompose2 <$> arbitraryOp2Ord <*> arbitraryOpBool <*> arbitraryOpBool)+ ]++-- | Generator of functions that take Bits and produce booleans.+arbitraryOpBoolBits :: (Typed a, Bits a) => Gen (Fun a Bool, [a] -> [Bool])+arbitraryOpBoolBits =+ frequency+ [ (1, funCompose2 <$> arbitraryOp2Eq <*> arbitraryOpBits <*> arbitraryOpBits)+ ]++-- | Generator of functions that take Nums and produce booleans.+arbitaryOpBoolOrdEqNum :: (Typed a, Eq a, Ord a, Num a)+ => Gen (Fun a Bool, [a] -> [Bool])+arbitaryOpBoolOrdEqNum =+ frequency+ [ (1, funCompose2 <$> arbitraryOp2Eq <*> arbitraryOpNum <*> arbitraryOpNum)+ , (1, funCompose2 <$> arbitraryOp2Ord <*> arbitraryOpNum <*> arbitraryOpNum)+ ]++-- | Generator of functions that take Floating point numbers and produce+-- booleans.+arbitraryOpBoolEqNumFloat :: (Typed t, Eq t, Num t, Floating t)+ => Gen (Fun t Bool, [t] -> [Bool])+arbitraryOpBoolEqNumFloat =+ frequency+ [ (1, funCompose2 <$> arbitraryOp2Eq <*> arbitraryOpNum <*> arbitraryOpFloat)+ , (1, funCompose2 <$> arbitraryOp2Eq <*> arbitraryOpFloat <*> arbitraryOpNum)+ ]++-- | Generator of functions that take and produce Bits.+arbitraryOpBits :: (Bits t, Typed t)+ => Gen (Fun t t, [t] -> [t])+arbitraryOpBits = elements+ [ (Op1 (BwNot typeOf), fmap complement)+ ]++-- | Generator of functions that take and produce Nums.+arbitraryOpNum :: (Typed t, Num t) => Gen (Fun t t, [t] -> [t])+arbitraryOpNum = elements+ [ (Op1 (Abs typeOf), fmap abs)+ , (Op1 (Sign typeOf), fmap signum)+ ]++-- | Generator of functions that take an arrays and indicates and produce+-- elements from the array.+arbitraryArrayIx :: forall t n . (Typed t, KnownNat n, Num t)+ => Gen ( Fun2 (Array n t) Word32 t+ , [Array n t] -> [Word32] -> [t]+ )+arbitraryArrayIx = return+ (Op2 (Index typeOf), zipWith (\x y -> arrayElems x !! fromIntegral y))++-- | Generator of functions on Floating point numbers.+arbitraryOpFloat :: (Floating t, Typed t) => Gen (Fun t t, [t] -> [t])+arbitraryOpFloat = elements+ [ (Op1 (Exp typeOf), fmap exp)+ , (Op1 (Sqrt typeOf), fmap sqrt)+ , (Op1 (Log typeOf), fmap log)+ , (Op1 (Sin typeOf), fmap sin)+ , (Op1 (Tan typeOf), fmap tan)+ , (Op1 (Cos typeOf), fmap cos)+ , (Op1 (Asin typeOf), fmap asin)+ , (Op1 (Atan typeOf), fmap atan)+ , (Op1 (Acos typeOf), fmap acos)+ , (Op1 (Sinh typeOf), fmap sinh)+ , (Op1 (Tanh typeOf), fmap tanh)+ , (Op1 (Cosh typeOf), fmap cosh)+ , (Op1 (Asinh typeOf), fmap asinh)+ , (Op1 (Atanh typeOf), fmap atanh)+ , (Op1 (Acosh typeOf), fmap acosh)+ ]++-- | Generator of functions on that produce elements of any type.+arbitraryOp1Any :: forall a b+ . (Arbitrary b, Typed a, Typed b)+ => Gen (Fun a b, [a] -> [b])+arbitraryOp1Any = oneof $+ [ (\v -> (\_ -> Const typeOf v, fmap (const v))) <$> arbitrary ]+ +++ rest+ where+ rest | Just Refl <- testEquality t1 t2+ = [return (id, id)]+ | otherwise+ = []++ t1 :: Type a+ t1 = typeOf++ t2 :: Type b+ t2 = typeOf++-- | Generator of functions on Booleans.+arbitraryOp1Bool :: Gen (Fun Bool Bool, [Bool] -> [Bool])+arbitraryOp1Bool = elements+ [ (Op1 Not, fmap not)+ ]++-- | Generator of binary functions on Booleans.+arbitraryOp2Bool :: Gen (Fun2 Bool Bool Bool, [Bool] -> [Bool] -> [Bool])+arbitraryOp2Bool = elements+ [ (Op2 And, zipWith (&&))+ , (Op2 Or, zipWith (||))+ ]++-- | Generator of binary functions that take two Eq elements of the same type+-- and return a Bool.+arbitraryOp2Eq :: (Typed t, Eq t)+ => Gen (Fun2 t t Bool, [t] -> [t] -> [Bool])+arbitraryOp2Eq = elements+ [ (Op2 (Eq typeOf), zipWith (==))+ , (Op2 (Ne typeOf), zipWith (/=))+ ]++-- | Generator of binary functions that take two Ord elements of the same type+-- and return a Bool.+arbitraryOp2Ord :: (Typed t, Ord t)+ => Gen (Fun2 t t Bool, [t] -> [t] -> [Bool])+arbitraryOp2Ord = elements+ [ (Op2 (Le typeOf), zipWith (<=))+ , (Op2 (Lt typeOf), zipWith (<))+ , (Op2 (Ge typeOf), zipWith (>=))+ , (Op2 (Gt typeOf), zipWith (>))+ ]++-- ** Random data generators++-- | Random array generator.+arbitraryArray :: forall n t . (KnownNat n, Random t) => Gen (Array n t)+arbitraryArray = array <$> vectorOf len chooseAny+ where+ len :: Int+ len = fromIntegral $ natVal (Proxy :: Proxy n)++-- ** Random test case generators++-- | Generator for test cases on integral numbers that produce booleans.+arbitraryOpIntegralBool :: (Typed t, Bounded t, Integral t, Bits t)+ => Gen (TestCase1 t Bool)+arbitraryOpIntegralBool = frequency+ [ (5, mkTestCase1+ arbitraryOpBool+ (chooseBoundedIntegral (minBound, maxBound)))++ , (2, mkTestCase1+ arbitraryOpBoolBits+ (chooseBoundedIntegral (minBound, maxBound)))++ -- we need to use +1 because certain operations overflow the number+ , (2, mkTestCase1+ arbitaryOpBoolOrdEqNum+ (chooseBoundedIntegral (minBound + 1, maxBound)))+ ]++-- | Generator for test cases on floating-point numbers that produce booleans.+arbitraryOpFloatingBool :: (Random t, Typed t, Floating t, Eq t)+ => Gen (TestCase1 t Bool)+arbitraryOpFloatingBool = oneof+ [ mkTestCase1 arbitraryOpBoolEqNumFloat chooseAny+ ]++-- | Generator for test cases on Arrays selection producing values of the+-- array.+arbitraryArrayNum :: forall n a+ . (KnownNat n, Num a, Random a, Typed a)+ => Gen (TestCase2 (Array n a) Word32 a)+arbitraryArrayNum = oneof+ [ mkTestCase2 arbitraryArrayIx arbitraryArray gen+ ]+ where+ gen :: Gen Word32+ gen = choose (0, len - 1)++ len :: Word32+ len = fromIntegral $ natVal (Proxy :: Proxy n)++-- * Semantics++-- ** Functions++-- | Unary Copilot function.+type Fun a b = Expr a -> Expr b++-- | Binary Copilot function.+type Fun2 a b c = Expr a -> Expr b -> Expr c++-- | Compose functions, paired with the Haskell functions that define their+-- idealized meaning.+funCompose1 :: (Fun b c, [b] -> [c])+ -> (Fun a b, [a] -> [b])+ -> (Fun a c, [a] -> [c])+funCompose1 (f1, g1) (f2, g2) = (f1 . f2, g1 . g2)++-- | Compose a binary function, with two functions, one for each argument.+funCompose2 :: (Fun2 b c d, [b] -> [c] -> [d])+ -> (Fun a b, [a] -> [b])+ -> (Fun a c, [a] -> [c])+ -> (Fun a d, [a] -> [d])+funCompose2 (f1, g1) (f2, g2) (f3, g3) =+ (uncurry f1 . (f2 &&& f3), uncurry g1 . (g2 &&& g3))++-- ** Test cases++-- | Test case specification for specs with one input variable and one output.+data TestCase1 a b = TestCase1+ { wrapTC1Expr :: Spec+ -- ^ Specification containing a trigger an extern of type 'a' and a trigger+ -- with an argument of type 'b'.++ , wrapTC1Fun :: [a] -> [b]+ -- ^ Function expected to function in the same way as the Spec being+ -- tested.++ , wrapTC1CopInp :: (String -> String, String, String, Gen a)+ -- ^ Input specification.+ --+ -- - The first element is a function that prints the variable declaration+ -- in C.+ --+ -- - The second element obtains a C expression that calculates the size of+ -- the variable in C.+ --+ -- - The third contains the variable name in C.+ --+ -- - The latter contains a randomized generator for values of the given+ -- type.++ , wrapTC1CopOut :: (String, String)+ -- ^ Output specification.+ --+ -- The first element of the tuple contains the type of the output in C.+ --+ -- The second element of the tuple is the formatting string when printing+ -- values of the given kind.+ }++-- | Test case specification for specs with two input variables and one output.+data TestCase2 a b c = TestCase2+ { wrapTC2Expr :: Spec+ -- ^ Specification containing a trigger an extern of type 'a' and a trigger+ -- with an argument of type 'b'.++ , wrapTC2Fun :: [a] -> [b] -> [c]+ -- ^ Function expected to function in the same way as the Spec being+ -- tested.++ , wrapTC2CopInp1 :: (String -> String, String, String, Gen a)+ -- ^ Input specification for the first input.+ --+ -- - The first element is a function that prints the variable declaration+ -- in C.+ --+ -- - The second element obtains a C expression that calculates the size of+ -- the variable in C.+ --+ -- - The third contains the variable name in C.+ --+ -- - The latter contains a randomized generator for values of the given+ -- type.++ , wrapTC2CopInp2 :: (String -> String, String, String, Gen b)+ -- ^ Input specification for the second input.+ --+ -- - The first element is a function that prints the variable declaration+ -- in C.+ --+ -- - The second element obtains a C expression that calculates the size of+ -- the variable in C.+ --+ -- - The third contains the variable name in C.+ --+ -- - The latter contains a randomized generator for values of the given+ -- type.++ , wrapTC2CopOut :: (String, String)+ -- ^ Output specification.+ --+ -- The first element of the tuple contains the type of the output in C.+ --+ -- The second element of the tuple is the formatting string when printing+ -- values of the given kind.+ }++-- | Generate test cases for expressions that behave like unary functions.+mkTestCase1 :: (Typed a, Typed b)+ => Gen (Fun a b, [a] -> [b])+ -> Gen a+ -> Gen (TestCase1 a b)+mkTestCase1 genO gen = do+ (copilotF, semF) <- genO++ let spec = alwaysTriggerArg1 (UExpr t2 appliedOp)+ appliedOp = copilotF (ExternVar t1 varName Nothing)++ return $+ TestCase1+ spec+ semF+ ( varDeclC t1, sizeC t1, varName, gen )+ ( typeC t2, formatC t2)++ where++ t1 = typeOf+ t2 = typeOf++ varName = "input"++-- | Generate test cases for expressions that behave like binary functions.+mkTestCase2 :: (Typed a, Typed b, Typed c)+ => Gen (Fun2 a b c, [a] -> [b] -> [c])+ -> Gen a+ -> Gen b+ -> Gen (TestCase2 a b c)+mkTestCase2 genO genA genB = do+ (copilotF, semF) <- genO++ let spec = alwaysTriggerArg1 (UExpr t3 appliedOp)+ appliedOp = copilotF (ExternVar t1 varName1 Nothing)+ (ExternVar t2 varName2 Nothing)++ return $+ TestCase2+ spec+ semF+ ( varDeclC t1, sizeC t1, varName1, genA )+ ( varDeclC t2, sizeC t2, varName2, genB )+ ( typeC t3, formatC t3)++ where++ t1 = typeOf+ t2 = typeOf+ t3 = typeOf++ varName1 = "input1"+ varName2 = "input2"++-- | Test running a compiled C program and comparing the results.+testRunCompare1 :: (Show a, CShow a, ReadableFromC b, Eq b)+ => Gen (TestCase1 a b) -> Property+testRunCompare1 ops =+ forAllBlind ops $ \testCase ->+ let (TestCase1 copilotSpec haskellFun inputVar outputVar) = testCase+ (cTypeInput, size, cInputName, gen) = inputVar++ in forAll (getPositive <$> arbitrary) $ \len ->++ forAll (vectorOf len gen) $ \nums -> do++ let inputs = [ (cTypeInput, size, fmap cshow nums, cInputName) ]+ outputs = haskellFun nums++ testRunCompareArg inputs len outputs copilotSpec outputVar++-- | Test running a compiled C program and comparing the results.+testRunCompare2 :: (Show a1, CShow a1, Show a2, CShow a2, ReadableFromC b, Eq b)+ => Gen (TestCase2 a1 a2 b) -> Property+testRunCompare2 ops =+ forAllBlind ops $ \testCase ->+ let (TestCase2 copilotSpec haskellFun inputVar1 inputVar2 outputVar) =+ testCase++ (cTypeInput1, size1, cInputName1, gen1) = inputVar1+ (cTypeInput2, size2, cInputName2, gen2) = inputVar2++ in forAll (getPositive <$> arbitrary) $ \len ->+ forAll (vectorOf len gen1) $ \nums1 ->+ forAll (vectorOf len gen2) $ \nums2 -> do+ let inputs = [ (cTypeInput1, size1, fmap cshow nums1, cInputName1)+ , (cTypeInput2, size2, fmap cshow nums2, cInputName2)+ ]++ outputs = haskellFun nums1 nums2++ testRunCompareArg inputs len outputs copilotSpec outputVar++-- | Test running a compiled C program and comparing the results, when the+-- program produces one output as an argument to a trigger that always fires.+--+-- PRE: all lists (second argument) of inputs have the length given as second+-- argument.+--+-- PRE: the monitoring code this is linked against uses the function+-- @printBack@ with exactly one argument to pass the results.+testRunCompareArg :: (ReadableFromC b, Eq b)+ => [(String -> String, String, [String], String)]+ -> Int+ -> [b]+ -> Spec+ -> (String, String)+ -> Property+testRunCompareArg inputs numInputs nums spec outputVar =+ ioProperty $ do+ tmpDir <- getTemporaryDirectory+ setCurrentDirectory tmpDir++ -- Operate in temporary directory+ testDir <- mkdtemp "copilot_test_"+ setCurrentDirectory testDir++ -- Produce copilot monitoring code+ compile "copilot_test" spec+ r <- compileC "copilot_test"++ -- Produce wrapper program+ let cProgram = testRunCompareArgCProgram inputs numInputs outputVar+ writeFile "main.c" cProgram++ -- Compile main program+ r2 <- compileExecutable "main" [ "copilot_test.o" ]++ -- Print result so far (for debugging purposes only)+ print r2+ print testDir++ -- Run program and compare result+ out <- readProcess "./main" [] ""+ let outNums = readFromC <$> lines out+ comparison = outNums == nums++ -- Only clean up if the test succeeded; otherwise, we want to inspect it.+ when comparison $ do+ -- Remove file produced by GCC+ removeFile "copilot_test.o"+ removeFile "main"++ -- Remove files produced "by hand"+ removeFile "main.c"++ -- Remove files produced by Copilot+ removeFile "copilot_test.c"+ removeFile "copilot_test.h"+ removeFile "copilot_test_types.h"++ -- Remove temporary directory+ setCurrentDirectory tmpDir+ removeDirectory testDir++ return $ r && r2 && comparison++-- | Return a wrapper C program that runs a loop for a number of iterations,+-- putting values in global variables at every step, running the monitors, and+-- publishing the results of any outputs.+testRunCompareArgCProgram+ :: [(String -> String, String, [String], String)]+ -> Int+ -> (String, String)+ -> String+testRunCompareArgCProgram inputs numSteps outputVar = unlines $+ [ "#include <stdio.h>"+ , "#include <stdint.h>"+ , "#include <stdbool.h>"+ , "#include <string.h>"+ , "#include <inttypes.h>"+ , "#include \"copilot_test.h\""+ , ""+ ]+ ++ varDecls +++ [ ""+ , "void printBack (" ++ cTypeRes ++ " num) {"+ , " printf(\"" ++ cStr ++ "\\n\", num);"+ , "}"+ , ""+ , "int main () {"+ , " int i = 0;"+ , " for (i = 0; i < " ++ maxInputsName ++ "; i++) {"+ ]+ ++ inputUpdates +++ [ ""+ , " step();"+ , " }"+ , " return 0;"+ , "}"+ ]++ where++ varDecls :: [String]+ varDecls =+ [ "int " ++ maxInputsName ++ " = " ++ show numSteps ++ ";" ]+ ++ inputVarDecls++ inputVarDecls :: [String]+ inputVarDecls =+ concatMap+ (\(ctypeF, _size, varName, arrVar, arrVals) ->++ let inputsStr = intercalate ", " (arrVals :: [String])++ in [ ctypeF (arrVar ++ "[]") ++ " = {" ++ inputsStr ++ "};"+ , ""+ , ctypeF varName ++ ";"+ ]+ )+ vars++ inputUpdates :: [String]+ inputUpdates = concatMap (\(_ctype, size, varName, arrVar, _arrVals) ->+ [ " memcpy(&" ++ varName ++ ", &" ++ arrVar ++ "[i], " ++ size ++ ");"+ ])+ vars++ (cTypeRes, cStr) = outputVar++ vars = map oneInput inputs+ oneInput (cTypeInput, size, inputVals, cInputName) =+ (cTypeInput, size, inputVarName, inputArrVarName, inputVals)+ where+ inputVarName = cInputName+ inputArrVarName = cInputName ++ "_s"++ maxInputsName = "MAX_STEPS"++-- * Auxiliary functions++-- ** Specs handling++-- | Build a 'Spec' that triggers at every step, passing the given expression+-- as argument, and execution a function 'printBack'.+alwaysTriggerArg1 :: UExpr -> Spec+alwaysTriggerArg1 = triggerArg1 (Const Bool True)++ where++ -- | Build a 'Spec' that triggers based on a given boolean stream, passing+ -- the given expression as argument, and execution a function 'printBack'.+ triggerArg1 :: Expr Bool -> UExpr -> Spec+ triggerArg1 guard expr = Spec streams observers triggers properties++ where++ streams = []+ observers = []+ properties = []++ triggers = [ Trigger function guard args ]+ function = "printBack"+ args = [ expr ]++-- ** Compilation of C programs++-- | Compile a C file given its basename.+compileC :: String -> IO Bool+compileC baseName = do+ result <- catch (do callProcess "gcc" [ "-c", baseName ++ ".c" ]+ return True+ )+ (\e -> do+ hPutStrLn stderr $+ "copilot-c99: error: compileC: cannot compile "+ ++ baseName ++ ".c with gcc"+ hPutStrLn stderr $+ "copilot-c99: exception: " ++ show (e :: IOException)+ return False+ )+ if result+ then doesFileExist $ baseName ++ ".o"+ else return False++-- | Compile a C file into an executable, given its basename and files to link+-- with it.+compileExecutable :: String -> [String] -> IO Bool+compileExecutable baseName linked = do+ result <- catch (do callProcess "gcc" $ [ baseName ++ ".c" ]+ ++ linked+ ++ [ "-lm" ]+ ++ [ "-o", baseName ]+ return True+ )+ (\e -> do+ hPutStrLn stderr $+ "copilot-c99: error: compileExecutable: cannot compile "+ ++ baseName ++ ".c with gcc"+ hPutStrLn stderr $+ "copilot-c99: exception: " ++ show (e :: IOException)+ return False+ )+ if result+ then doesFileExist baseName+ else return False++-- ** Interfacing between Haskell and C++-- | C formatting string that can be used to print values of a given type.+formatC :: Typed a => Type a -> String+formatC Bool = "%d"+formatC Int8 = "%d"+formatC Int16 = "%d"+formatC Int32 = "%d"+formatC Int64 = "%ld"+formatC Word8 = "%d"+formatC Word16 = "%d"+formatC Word32 = "%d"+formatC Word64 = "%ld"+formatC Float = "%f"+formatC Double = "%lf"+formatC _ = error+ "copilot-c99 (test): Printing of arrays and structs is not yet supported."++-- | C type used to store values of a given type.+typeC :: Typed a => Type a -> String+typeC Bool = "bool"+typeC Int8 = "int8_t"+typeC Int16 = "int16_t"+typeC Int32 = "int32_t"+typeC Int64 = "int64_t"+typeC Word8 = "uint8_t"+typeC Word16 = "uint16_t"+typeC Word32 = "uint32_t"+typeC Word64 = "uint64_t"+typeC Float = "float"+typeC Double = "double"+typeC (Array tE) = typeC tE ++ "[]"+typeC _ = error+ "copilot-c99 (test): Input variables of type struct are not yet supported."++-- | C variable declaration for values of a given type.+varDeclC :: Typed a => Type a -> String -> String+varDeclC Bool v = "bool " ++ v+varDeclC Int8 v = "int8_t " ++ v+varDeclC Int16 v = "int16_t " ++ v+varDeclC Int32 v = "int32_t " ++ v+varDeclC Int64 v = "int64_t " ++ v+varDeclC Word8 v = "uint8_t " ++ v+varDeclC Word16 v = "uint16_t " ++ v+varDeclC Word32 v = "uint32_t " ++ v+varDeclC Word64 v = "uint64_t " ++ v+varDeclC Float v = "float " ++ v+varDeclC Double v = "double " ++ v+varDeclC t@(Array tE) v =+ typeC tE ++ " " ++ v ++ "[" ++ show (typeLength t) ++ "]"+varDeclC _ _ = error+ "copilot-c99 (test): Input variables of type struct are not yet supported."++-- | Expression that calculates the size of a variable of a given type.+sizeC :: Typed a => Type a -> String+sizeC Bool = "sizeof(bool)"+sizeC Int8 = "sizeof(int8_t)"+sizeC Int16 = "sizeof(int16_t)"+sizeC Int32 = "sizeof(int32_t)"+sizeC Int64 = "sizeof(int64_t)"+sizeC Word8 = "sizeof(uint8_t)"+sizeC Word16 = "sizeof(uint16_t)"+sizeC Word32 = "sizeof(uint32_t)"+sizeC Word64 = "sizeof(uint64_t)"+sizeC Float = "sizeof(float)"+sizeC Double = "sizeof(double)"+sizeC t@(Array tE) = show (typeLength t) ++ "* sizeof(" ++ typeC tE ++ ")"+sizeC _ = error+ "copilot-c99 (test): Input variables of type struct are not yet supported."++-- | Show a value of a given type in C.+class CShow s where+ cshow :: s -> String++instance CShow Int8 where+ -- Use a macro to ensure that any necessary suffixes are added to the number.+ -- We choose this macro instead of specifically adding a suffix for reasons+ -- of portability.+ cshow x = "INT8_C(" ++ show x ++ ")"++instance CShow Int16 where+ -- Use a macro to ensure that any necessary suffixes are added to the number.+ -- We choose this macro instead of specifically adding a suffix for reasons+ -- of portability.+ cshow x = "INT16_C(" ++ show x ++ ")"++instance CShow Int32 where+ -- Use a macro to ensure that any necessary suffixes are added to the number.+ -- We choose this macro instead of specifically adding a suffix for reasons+ -- of portability.+ cshow x = "INT32_C(" ++ show x ++ ")"++instance CShow Int64 where+ -- Use a macro to ensure that any necessary suffixes are added to the number.+ -- We choose this macro instead of specifically adding a suffix for reasons+ -- of portability.+ cshow x = "INT64_C(" ++ show x ++ ")"++instance CShow Word8 where+ -- Use a macro to ensure that any necessary suffixes are added to the number.+ -- We choose this macro instead of specifically adding a suffix for reasons+ -- of portability.+ cshow x = "UINT8_C(" ++ show x ++ ")"++instance CShow Word16 where+ -- Use a macro to ensure that any necessary suffixes are added to the number.+ -- We choose this macro instead of specifically adding a suffix for reasons+ -- of portability.+ cshow x = "UINT16_C(" ++ show x ++ ")"++instance CShow Word32 where+ -- Use a macro to ensure that any necessary suffixes are added to the number.+ -- We choose this macro instead of specifically adding a suffix for reasons+ -- of portability.+ cshow x = "UINT32_C(" ++ show x ++ ")"++instance CShow Word64 where+ -- Use a macro to ensure that any necessary suffixes are added to the number.+ -- We choose this macro instead of specifically adding a suffix for reasons+ -- of portability.+ cshow x = "UINT64_C(" ++ show x ++ ")"++instance CShow Float where+ cshow = show++instance CShow Double where+ cshow = show++instance CShow Bool where+ cshow True = "true"+ cshow False = "false"++instance CShow t => CShow (Array n t) where+ cshow a = intercalate "," $ map cshow $ arrayElems a++-- | Read a value of a given type in C.+class ReadableFromC a where+ readFromC :: String -> a++instance ReadableFromC Bool where+ readFromC "0" = False+ readFromC _ = True++instance ReadableFromC Int8 where+ readFromC = read++instance ReadableFromC Int16 where+ readFromC = read++instance ReadableFromC Int32 where+ readFromC = read++instance ReadableFromC Int64 where+ readFromC = read++instance ReadableFromC Word8 where+ readFromC = read++instance ReadableFromC Word16 where+ readFromC = read++instance ReadableFromC Word32 where+ readFromC = read++instance ReadableFromC Word64 where+ readFromC = read