Skip to content

Commit 4620006

Browse files
Merge branch 'develop-fix-trigger-arg-arrays'. Close #431.
**Description** Copilot's C99 backend generates incorrect code. In the generated code, the functions that generate values passed as arguments to trigger functions may return pointers to stack-allocated arrays, so the memory may be used for something else (or freed). Instead, the structure of the implementation should guarantee that the arrays are always allocated. **Type** - Bug: generated C code may be incorrect. **Additional context** - Bug #386 was initially opened for the same reason, but only addressed the `_gen` functions generated by Copilot. Other functions generated by Copilot also return pointers to arrays and can fail for the same reason. **Requester** - Ryan Scott (Galois) **Method to check presence of bug** The following Dockerfile, and accompanying Copilot and C code, check that passing a stream of arrays as an argument to a trigger, when compiled with and without optimizations, produces the same results, in which case it prints the message Success. The docker image produces a diff of the two outputs if not: ``` --- Dockerfile-verify-431 FROM ubuntu:trusty RUN apt-get update RUN apt-get install --yes software-properties-common RUN add-apt-repository ppa:hvr/ghc RUN apt-get update RUN apt-get install --yes ghc-8.6.5 cabal-install-2.4 RUN apt-get install --yes libz-dev ENV PATH=/opt/ghc/8.6.5/bin:/opt/cabal/2.4/bin:$PWD/.cabal-sandbox/bin:$PATH RUN cabal update RUN cabal v1-sandbox init RUN cabal v1-install alex happy RUN apt-get install --yes git ADD Array.hs /tmp/Array.hs ADD array_driver.c /tmp/array_driver.c SHELL ["/bin/bash", "-c"] CMD git clone $REPO && cd $NAME && git checkout $COMMIT && cd .. \ && cabal v1-install $NAME/copilot**/ \ && cabal v1-exec -- runhaskell /tmp/Array.hs \ && gcc -O0 /tmp/array_driver.c array.c -I. -o main-no-op && ./main-no-op > no-optimizations \ && gcc -O /tmp/array_driver.c array.c -I. -o main-op && ./main-op > with-optimizations \ && diff no-optimizations with-optimizations \ && echo "Success" --- Array.hs {-# LANGUAGE DataKinds #-} module Main where import Copilot.Compile.C99 import Language.Copilot import qualified Prelude as P spec :: Spec spec = trigger "f" true [arg stream] where stream :: Stream (Array 2 Int16) stream = constant (array [3,4]) main :: IO () main = reify spec >>= compile "array" --- array_driver.c #include <stdint.h> #include <stdio.h> #include "array_types.h" #include "array.h" void f (int16_t f_arg0[(2)]) { printf("([%d,%d])\n", f_arg0[0], f_arg0[1]); } int main(void) { int i = 0; printf("f:\n"); for (i=0; i<5; i++) { step(); } return 0; } ``` Command (substitute variables based on new path after merge): ``` $ docker run -e "REPO=https://github.com/Copilot-Language/copilot" -e "NAME=copilot" -e "COMMIT=<HASH>" -it copilot-verify-431 ``` **Expected result** Running the docker image above prints the message success, indicating that the output structs produced with and without optimizations enabled are the same. **Solution implemented** Modify generated C99 backend so that the output array in a function producing values passed to triggers as arguments, as well as any they call that also produces arrays, take the returned array as input. That way, all memory is preallocated and there's no need for mallocs either (which we want to avoid). Identify factorization opportunity for different functions that call generators and pattern match based on the type of the expression being generated. **Further notes** None.
2 parents 907d5bb + e975b4a commit 4620006

File tree

3 files changed

+36
-31
lines changed

3 files changed

+36
-31
lines changed

copilot-c99/CHANGELOG

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
2023-12-17
1+
2023-12-19
22
* Change return type of main generated for tests. (#468)
33
* Print constants in tests using portable suffixes. (#471).
4+
* Pass output arrays as arguments to trigger argument functions. (#431)
45

56
2023-11-07
67
* Version bump (3.17). (#466)

copilot-c99/src/Copilot/Compile/C99/CodeGen.hs

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -163,17 +163,24 @@ mkStep cSettings streams triggers exts =
163163
(triggerDeclns, triggerStmts) =
164164
unzip $ map mkTriggerCheck triggers
165165

166+
-- Update the value of a variable with the result of calling a function that
167+
-- generates the next value in a stream expression. If the type of the
168+
-- variable is an array, then we cannot perform a direct C assignment, so
169+
-- we instead pass the variable as an output array to the function.
170+
updateVar :: C.Ident -> C.Ident -> Type a -> C.Expr
171+
updateVar varName genName (Array _) =
172+
C.Funcall (C.Ident genName) [C.Ident varName]
173+
updateVar varName genName _ =
174+
C.AssignOp C.Assign (C.Ident varName) (C.Funcall (C.Ident genName) [])
175+
166176
-- Write code to update global stream buffers and index.
167177
mkUpdateGlobals :: Stream -> (C.Decln, C.Stmt, C.Stmt, C.Stmt)
168178
mkUpdateGlobals (Stream sId buff _expr ty) =
169179
(tmpDecln, tmpAssign, bufferUpdate, indexUpdate)
170180
where
171181
tmpDecln = C.VarDecln Nothing cTy tmpVar Nothing
172182

173-
tmpAssign = case ty of
174-
Array _ -> C.Expr $ C.Funcall (C.Ident $ generatorName sId)
175-
[ C.Ident tmpVar ]
176-
_ -> C.Expr $ C.Ident tmpVar C..= val
183+
tmpAssign = C.Expr $ updateVar tmpVar (generatorName sId) ty
177184

178185
bufferUpdate = case ty of
179186
Array _ -> C.Expr $ memcpy dest (C.Ident tmpVar) size
@@ -193,7 +200,6 @@ mkStep cSettings streams triggers exts =
193200
tmpVar = streamName sId ++ "_tmp"
194201
buffVar = C.Ident $ streamName sId
195202
indexVar = C.Ident $ indexName sId
196-
val = C.Funcall (C.Ident $ generatorName sId) []
197203
cTy = transType ty
198204

199205
-- Make code that copies an external variable to its local one.
@@ -250,22 +256,8 @@ mkStep cSettings streams triggers exts =
250256
aTmpDeclns = zipWith declare args aTempNames
251257
where
252258
declare :: UExpr -> C.Ident -> C.Decln
253-
declare arg tmpVar =
254-
C.VarDecln Nothing (tempType arg) tmpVar Nothing
255-
256-
-- Type of the temporary variable used to store values of the type
257-
-- of a given expression.
258-
tempType :: UExpr -> C.Type
259-
tempType (UExpr { uExprType = ty }) =
260-
case ty of
261-
-- If a temporary variable is being used to store an array,
262-
-- declare the type of the temporary variable as a pointer, not
263-
-- an array. The problem with declaring it as an array is that
264-
-- the `arg` function will return a pointer, not an array, and
265-
-- C doesn't make it easy to cast directly from an array to a
266-
-- pointer.
267-
Array ty' -> C.Ptr $ transType ty'
268-
_ -> transType ty
259+
declare (UExpr { uExprType = ty }) tmpVar =
260+
C.VarDecln Nothing (transType ty) tmpVar Nothing
269261

270262
triggerCheckStmt :: C.Stmt
271263
triggerCheckStmt = C.If guard' fireTrigger
@@ -283,13 +275,14 @@ mkStep cSettings streams triggers exts =
283275
where
284276
-- List of assignments of values of temporary variables.
285277
argAssigns :: [C.Expr]
286-
argAssigns = zipWith assign aTempNames args'
278+
argAssigns = zipWith3 assign aTempNames aArgNames args
287279

288-
assign :: C.Ident -> C.Expr -> C.Expr
289-
assign aTempName = C.AssignOp C.Assign (C.Ident aTempName)
280+
assign :: C.Ident -> C.Ident -> UExpr -> C.Expr
281+
assign aTempName aArgName (UExpr { uExprType = ty }) =
282+
updateVar aTempName aArgName ty
290283

291-
args' = take (length args) (map argCall (argNames name))
292-
argCall name' = C.Funcall (C.Ident name') []
284+
aArgNames :: [C.Ident]
285+
aArgNames = take (length args) (argNames name)
293286

294287
-- Build an expression to pass a temporary variable as argument
295288
-- to a trigger handler.

copilot-c99/src/Copilot/Compile/C99/Compile.hs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,8 @@ compileC cSettings spec = C.TransUnit declns funs
119119
accessDecln (Stream sId buff _ ty) = mkAccessDecln sId ty buff
120120

121121
streamGen :: Stream -> C.FunDef
122-
streamGen (Stream sId _ expr ty@(Array _)) =
123-
mkGenFunArray (generatorName sId) (generatorOutputArgName sId) expr ty
124-
streamGen (Stream sId _ expr ty) = mkGenFun (generatorName sId) expr ty
122+
streamGen (Stream sId _ expr ty) =
123+
exprGen (generatorName sId) (generatorOutputArgName sId) expr ty
125124

126125
triggerGen :: Trigger -> [C.FunDef]
127126
triggerGen (Trigger name guard args) = guardDef : argDefs
@@ -130,7 +129,19 @@ compileC cSettings spec = C.TransUnit declns funs
130129
argDefs = zipWith argGen (argNames name) args
131130

132131
argGen :: String -> UExpr -> C.FunDef
133-
argGen argName (UExpr ty expr) = mkGenFun argName expr ty
132+
argGen argName (UExpr ty expr) =
133+
exprGen argName (argName ++ "_output") expr ty
134+
135+
-- Create a function that calculates the current value generated by an
136+
-- expression `expr` of type `ty`. The generator treats arrays
137+
-- specially, and the function takes an output array as a parameter.
138+
-- The second identifier `outputArrName` is not used if `expr` is not an
139+
-- array.
140+
exprGen :: C.Ident -> C.Ident -> Expr a -> Type a -> C.FunDef
141+
exprGen funName outputArrName expr ty@(Array _) =
142+
mkGenFunArray funName outputArrName expr ty
143+
exprGen funName _ expr ty =
144+
mkGenFun funName expr ty
134145

135146
-- | Generate the .h file from a 'Spec'.
136147
compileH :: CSettings -> Spec -> C.TransUnit

0 commit comments

Comments
 (0)