Skip to content

Commit 7c1049a

Browse files
Merge branch 'develop-update-structs'. Close #520.
**Description** Copilot currently supports reading values from fields of structs, but not changing them. We'd like to be able to modify a struct by adjusting the values of specific fields. **Type** - Feature: new extension to the language. **Additional context** None. **Requester** - Ivan Perez **Method to check presence of bug** Not applicable (not a bug). **Expected result** Copilot provides a mechanism to update the field of a struct. The following Dockerfile installs copilot and runs a file with struct update operations, which it then links and compares against its expected output, printing the message "Success" if the output matches the expectation: ``` --- Dockerfile FROM ubuntu:focal ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update RUN apt-get install --yes libz-dev RUN apt-get install --yes git RUN apt-get install --yes wget RUN mkdir -p $HOME/.ghcup/bin RUN wget https://downloads.haskell.org/~ghcup/0.1.19.2/x86_64-linux-ghcup-0.1.19.2 -O $HOME/.ghcup/bin/ghcup RUN chmod a+x $HOME/.ghcup/bin/ghcup ENV PATH=$PATH:/root/.ghcup/bin/ ENV PATH=$PATH:/root/.cabal/bin/ RUN apt-get install --yes curl RUN apt-get install --yes gcc g++ make libgmp3-dev RUN apt-get install --yes pkg-config SHELL ["/bin/bash", "-c"] RUN ghcup install ghc 9.4 RUN ghcup install cabal 3.2 RUN ghcup set ghc 9.4.8 RUN cabal update ADD Structs.hs /tmp/Structs.hs ADD main.c /tmp/main.c ADD expected /tmp/expected CMD git clone $REPO \ && cd $NAME \ && git checkout $COMMIT \ && cabal v1-sandbox init \ && cabal v1-install alex happy \ && cabal v1-install copilot**/ \ && cabal v1-exec -- runhaskell /tmp/Structs.hs \ && mv /tmp/main.c . \ && gcc main.c structs.c -o main \ && ./main > actual \ && diff actual /tmp/expected \ && echo Success --- Structs.hs {-# LANGUAGE DataKinds #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE NoImplicitPrelude #-} module Main (main) where import Copilot.Compile.C99 import Data.Type.Equality as DE import Language.Copilot -- * Struct type definitions data SoA = SoA { arr :: Field "arr" SoB } instance Struct SoA where typeName _ = "soa" toValues soa = [Value typeOf (arr soa)] instance Typed SoA where typeOf = Struct $ SoA $ Field undefined data SoB = SoB { arr2 :: Field "arr2" (Array 3 Float) } instance Struct SoB where typeName _ = "sob" toValues sob = [Value typeOf (arr2 sob)] instance Typed SoB where typeOf = Struct $ SoB $ Field undefined data SoC = SoC { arr3 :: Field "arr3" Int32 } -- | Struct with a custom value for updates instance Struct SoC where typeName _ = "soc" toValues soc = [Value typeOf (arr3 soc)] updateField s (Value ty f@(Field v)) = case fieldName f of "arr3" -> case testEquality ty Int32 of Just DE.Refl -> s { arr3 = Field v } _ -> error "The value provided for arr3 does not have type Int32" _ -> error "The field name provided does not exist in SoC" instance Typed SoC where typeOf = Struct $ SoC $ Field undefined -- Sample values -- We purposefully leave some type signatures out to check that GHC can infer -- them. -- SoAs a1 = SoA $ Field $ SoB $ Field $ array [0, 1, 2] b1 = SoB $ Field $ array [10, 20, 30] b2 = SoB $ Field $ array [40, 50, 60] c1 = SoC $ Field 5 soa :: Stream SoA soa = constant a1 soa1 = soa ## arr =: recursiveArray soa2 = soa ## arr =: constant b1 soarr = soa ## arr =: soa # arr soc1 = constant c1 ## arr3 =: counter soc2 = constant c1 ## arr3 =$ (+5) recursiveArray :: Stream SoB recursiveArray = [b1, b2] ++ recursiveArray counter :: Stream Int32 counter = [55] ++ (counter + 1) spec :: Spec spec = do trigger "arrays" (soa1 # arr # arr2 .!! 1 /= 60) [arg soa, arg soa1, arg soa2] trigger "arrays2" true [arg soc1] trigger "arrays3" true [arg soc2] main :: IO () main = do spec' <- reify spec compile "structs" spec' --- main.c #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include "structs.h" #include "structs_types.h" void arrays( struct soa * arrays_arg0 , struct soa * arrays_arg1 , struct soa * arrays_arg2 ) { printf("0: %f %f %f\n" , arrays_arg0->arr.arr2[0] , arrays_arg0->arr.arr2[1] , arrays_arg0->arr.arr2[2]); printf("1: %f %f %f\n" , arrays_arg1->arr.arr2[0] , arrays_arg1->arr.arr2[1] , arrays_arg1->arr.arr2[2]); printf("2: %f %f %f\n" , arrays_arg2->arr.arr2[0] , arrays_arg2->arr.arr2[1] , arrays_arg2->arr.arr2[2]); } void arrays2(struct soc * arrays2_arg0) { printf("soc1: %d\n", arrays2_arg0->arr3); } void arrays3(struct soc * arrays2_arg0) { printf("soc2: %d\n", arrays2_arg0->arr3); } int main() { step(); step(); step(); step(); } --- expected 0: 0.000000 1.000000 2.000000 1: 10.000000 20.000000 30.000000 2: 10.000000 20.000000 30.000000 soc1: 55 soc2: 10 0: 0.000000 1.000000 2.000000 1: 40.000000 50.000000 60.000000 2: 10.000000 20.000000 30.000000 soc1: 56 soc2: 10 0: 0.000000 1.000000 2.000000 1: 10.000000 20.000000 30.000000 2: 10.000000 20.000000 30.000000 soc1: 57 soc2: 10 0: 0.000000 1.000000 2.000000 1: 40.000000 50.000000 60.000000 2: 10.000000 20.000000 30.000000 soc1: 58 soc2: 10 ``` 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-520 ``` **Solution implemented** Extend `copilot-language` to provide operations to modify fields of structs, and adjust `copilot-core`, `copilot-c99` and `copilot-interpreter` to support such operations. **Further notes** We choose to temporarily limit the operation to values of fields that can be Shown, due to an overlapping instance for Show that it is difficult to avoid. We will revisit this point in the future.
2 parents 320b471 + 5fb6d60 commit 7c1049a

File tree

10 files changed

+217
-51
lines changed

10 files changed

+217
-51
lines changed

copilot-c99/CHANGELOG

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
2024-07-07
2+
* Add support for struct field updates. (#520)
3+
14
2024-05-07
25
* Version bump (3.19.1). (#512)
36

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

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,13 @@ mkAccessDecln sId ty xs =
121121
-- | Write a generator function for a stream.
122122
mkGenFun :: String -> Expr a -> Type a -> C.FunDef
123123
mkGenFun name expr ty =
124-
C.FunDef static cTy name [] cVars [C.Return $ Just cExpr]
124+
C.FunDef static cTy name [] cVars stmts
125125
where
126-
static = Just C.Static
127-
cTy = C.decay $ transType ty
128-
(cExpr, cVars) = runState (transExpr expr) mempty
126+
static = Just C.Static
127+
cTy = C.decay $ transType ty
128+
(cExpr, state') = runState (transExpr expr) (0, mempty, mempty)
129+
(_, cVars, stmts') = state'
130+
stmts = stmts' ++ [C.Return $ Just cExpr]
129131

130132
-- | Write a generator function for a stream that returns an array.
131133
mkGenFunArray :: String -> String -> Expr a -> Type a -> C.FunDef
@@ -139,13 +141,20 @@ mkGenFunArray name nameArg expr ty@(Array _) =
139141
outputParam = C.Param cArrayType nameArg
140142
cArrayType = transType ty
141143

142-
-- Output value, and any variable declarations needed
143-
(cExpr, varDecls) = runState (transExpr expr) mempty
144+
-- Statements to execute as part of the generator. They include statements
145+
-- related to calculating the values of intermediate expressions, and
146+
-- statements related to copying the final value to the destination array.
147+
stmts = stmts' ++ [ copyStmt ]
144148

145-
-- Copy expression to output argument
146-
stmts = [ C.Expr $ memcpy (C.Ident nameArg) cExpr size ]
147-
size = C.LitInt (fromIntegral $ typeSize ty)
148-
C..* C.SizeOfType (C.TypeName $ tyElemName ty)
149+
-- Output value, variable declarations needed, and other statements that
150+
-- must be executed to calculate intermediate values.
151+
(cExpr, state') = runState (transExpr expr) (0, mempty, mempty)
152+
(_, varDecls, stmts') = state'
153+
154+
-- Copy expression to output argument.
155+
copyStmt = C.Expr $ memcpy (C.Ident nameArg) cExpr size
156+
size = C.LitInt (fromIntegral $ typeSize ty)
157+
C..* C.SizeOfType (C.TypeName $ tyElemName ty)
149158

150159
mkGenFunArray _name _nameArg _expr _ty =
151160
impossible "mkGenFunArray" "copilot-c99"

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

Lines changed: 91 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,20 @@ module Copilot.Compile.C99.Expr
88
where
99

1010
-- External imports
11-
import Control.Monad.State ( State, modify )
11+
import Control.Monad.State ( State, get, modify )
1212
import qualified Data.List.NonEmpty as NonEmpty
1313
import qualified Language.C99.Simple as C
1414

1515
-- Internal imports: Copilot
1616
import Copilot.Core ( Expr (..), Field (..), Op1 (..), Op2 (..), Op3 (..),
1717
Type (..), Value (..), accessorName, arrayElems,
18-
toValues )
18+
toValues, typeSize )
1919

2020
-- Internal imports
2121
import Copilot.Compile.C99.Error ( impossible )
2222
import Copilot.Compile.C99.Name ( exCpyName, streamAccessorName )
23-
import Copilot.Compile.C99.Type ( transLocalVarDeclType, transTypeName )
23+
import Copilot.Compile.C99.Type ( transLocalVarDeclType, transType,
24+
transTypeName )
2425

2526
-- | Translates a Copilot Core expression into a C99 expression.
2627
transExpr :: Expr a -> State FunEnv C.Expr
@@ -32,7 +33,9 @@ transExpr (Local ty1 _ name e1 e2) = do
3233
initExpr = Just $ C.InitExpr e1'
3334

3435
-- Add new decl to the tail of the fun env
35-
modify (++ [C.VarDecln Nothing cTy1 name initExpr])
36+
modify (\(i, x, y)
37+
-> (i, x ++ [C.VarDecln Nothing cTy1 name initExpr], y)
38+
)
3639

3740
transExpr e2
3841

@@ -51,6 +54,48 @@ transExpr (Op1 op e) = do
5154
e' <- transExpr e
5255
return $ transOp1 op e'
5356

57+
transExpr (Op2 (UpdateField ty1@(Struct _) ty2 f) e1 e2) = do
58+
-- Translating a struct update Op requires initializing a variable to the
59+
-- "old" value, updating the field, and returning the new value.
60+
e1' <- transExpr e1
61+
e2' <- transExpr e2
62+
63+
-- Variable to hold the updated struct
64+
(i, _, _) <- get
65+
let varName = "_v" ++ show i
66+
modify (\(i, x, y) -> (i + 1, x, y))
67+
68+
-- Add new var decl
69+
let initDecl = C.VarDecln Nothing cTy1 varName Nothing
70+
cTy1 = transLocalVarDeclType ty1
71+
modify (\(i, x, y) -> (i, x ++ [initDecl], y))
72+
73+
-- Initialize the var to the same value as the original struct
74+
let initStmt = C.Expr
75+
$ C.AssignOp
76+
C.Assign
77+
(C.Ident varName)
78+
e1'
79+
80+
-- Update field f with given value e2.
81+
let updateStmt = case ty2 of
82+
Array _ -> C.Expr $ memcpy dest e2' size
83+
where
84+
dest = C.Dot (C.Ident varName) (accessorName f)
85+
size = C.LitInt
86+
(fromIntegral $ typeSize ty2)
87+
C..* C.SizeOfType (C.TypeName (tyElemName ty2))
88+
89+
_ -> C.Expr
90+
$ C.AssignOp
91+
C.Assign
92+
(C.Dot (C.Ident varName) (accessorName f))
93+
e2'
94+
95+
modify (\(i, x, y) -> (i, x, y ++ [ initStmt, updateStmt ]))
96+
97+
return $ C.Ident varName
98+
5499
transExpr (Op2 op e1 e2) = do
55100
e1' <- transExpr e1
56101
e2' <- transExpr e2
@@ -100,32 +145,35 @@ transOp1 op e =
100145

101146
-- | Translates a Copilot binary operator and its arguments into a C99
102147
-- expression.
148+
--
149+
-- PRE: op is not a struct update operation (i.e., 'UpdateField').
103150
transOp2 :: Op2 a b c -> C.Expr -> C.Expr -> C.Expr
104151
transOp2 op e1 e2 = case op of
105-
And -> e1 C..&& e2
106-
Or -> e1 C..|| e2
107-
Add _ -> e1 C..+ e2
108-
Sub _ -> e1 C..- e2
109-
Mul _ -> e1 C..* e2
110-
Mod _ -> e1 C..% e2
111-
Div _ -> e1 C../ e2
112-
Fdiv _ -> e1 C../ e2
113-
Pow ty -> funCall (specializeMathFunName ty "pow") [e1, e2]
114-
Logb ty -> funCall (specializeMathFunName ty "log") [e2] C../
115-
funCall (specializeMathFunName ty "log") [e1]
116-
Atan2 ty -> funCall (specializeMathFunName ty "atan2") [e1, e2]
117-
Eq _ -> e1 C..== e2
118-
Ne _ -> e1 C..!= e2
119-
Le _ -> e1 C..<= e2
120-
Ge _ -> e1 C..>= e2
121-
Lt _ -> e1 C..< e2
122-
Gt _ -> e1 C..> e2
123-
BwAnd _ -> e1 C..& e2
124-
BwOr _ -> e1 C..| e2
125-
BwXor _ -> e1 C..^ e2
126-
BwShiftL _ _ -> e1 C..<< e2
127-
BwShiftR _ _ -> e1 C..>> e2
128-
Index _ -> C.Index e1 e2
152+
And -> e1 C..&& e2
153+
Or -> e1 C..|| e2
154+
Add _ -> e1 C..+ e2
155+
Sub _ -> e1 C..- e2
156+
Mul _ -> e1 C..* e2
157+
Mod _ -> e1 C..% e2
158+
Div _ -> e1 C../ e2
159+
Fdiv _ -> e1 C../ e2
160+
Pow ty -> funCall (specializeMathFunName ty "pow") [e1, e2]
161+
Logb ty -> funCall (specializeMathFunName ty "log") [e2] C../
162+
funCall (specializeMathFunName ty "log") [e1]
163+
Atan2 ty -> funCall (specializeMathFunName ty "atan2") [e1, e2]
164+
Eq _ -> e1 C..== e2
165+
Ne _ -> e1 C..!= e2
166+
Le _ -> e1 C..<= e2
167+
Ge _ -> e1 C..>= e2
168+
Lt _ -> e1 C..< e2
169+
Gt _ -> e1 C..> e2
170+
BwAnd _ -> e1 C..& e2
171+
BwOr _ -> e1 C..| e2
172+
BwXor _ -> e1 C..^ e2
173+
BwShiftL _ _ -> e1 C..<< e2
174+
BwShiftR _ _ -> e1 C..>> e2
175+
Index _ -> C.Index e1 e2
176+
UpdateField _ _ _ -> impossible "transOp2" "copilot-c99"
129177

130178
-- | Translates a Copilot ternary operator and its arguments into a C99
131179
-- expression.
@@ -368,10 +416,24 @@ typeIsFloating _ = False
368416
-- | Auxiliary type used to collect all the declarations of all the variables
369417
-- used in a function to be generated, since variable declarations are always
370418
-- listed first at the top of the function body.
371-
type FunEnv = [C.Decln]
419+
type FunEnv = (Int, [C.Decln], [C.Stmt])
372420

373421
-- | Define a C expression that calls a function with arguments.
374422
funCall :: C.Ident -- ^ Function name
375423
-> [C.Expr] -- ^ Arguments
376424
-> C.Expr
377425
funCall name = C.Funcall (C.Ident name)
426+
427+
-- Write a call to the memcpy function.
428+
memcpy :: C.Expr -> C.Expr -> C.Expr -> C.Expr
429+
memcpy dest src size = C.Funcall (C.Ident "memcpy") [dest, src, size]
430+
431+
-- Translate a Copilot type to a C99 type, handling arrays especially.
432+
--
433+
-- If the given type is an array (including multi-dimensional arrays), the
434+
-- type is that of the elements in the array. Otherwise, it is just the
435+
-- equivalent representation of the given type in C.
436+
tyElemName :: Type a -> C.Type
437+
tyElemName ty = case ty of
438+
Array ty' -> tyElemName ty'
439+
_ -> transType ty

copilot-core/CHANGELOG

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
2024-07-07
2+
* Update Op2, Struct to support struct field updates. (#520)
3+
14
2024-05-07
25
* Version bump (3.19.1). (#512)
36

copilot-core/src/Copilot/Core/Operators.hs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ module Copilot.Core.Operators
1313
where
1414

1515
-- External imports
16-
import Data.Bits (Bits)
17-
import Data.Word (Word32)
18-
import GHC.TypeLits (KnownSymbol)
16+
import Data.Bits (Bits)
17+
import Data.Typeable (Typeable)
18+
import Data.Word (Word32)
19+
import GHC.TypeLits (KnownSymbol)
1920

2021
-- Internal imports
2122
import Copilot.Core.Type (Field (..), Type (..))
@@ -96,6 +97,11 @@ data Op2 a b c where
9697
Index :: Type (Array n t) -> Op2 (Array n t) Word32 t
9798
-- ^ Array access/projection of an array element.
9899

100+
-- Struct operator.
101+
UpdateField :: (Typeable b, KnownSymbol s, Show b)
102+
=> Type a -> Type b -> (a -> Field s b) -> Op2 a b a
103+
-- ^ Update a field of a struct.
104+
99105
-- | Ternary operators.
100106
data Op3 a b c d where
101107
-- Conditional operator.

copilot-core/src/Copilot/Core/Type.hs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ module Copilot.Core.Type
3434
, Struct
3535
, fieldName
3636
, accessorName
37+
, updateField
3738
)
3839
where
3940

@@ -59,6 +60,9 @@ class Struct a where
5960
-- | Transforms all the struct's fields into a list of values.
6061
toValues :: a -> [Value a]
6162

63+
updateField :: a -> Value t -> a
64+
updateField = error "Field updates not supported for this type."
65+
6266
-- | The field of a struct, together with a representation of its type.
6367
data Value a =
6468
forall s t . (Typeable t, KnownSymbol s, Show t) => Value (Type t) (Field s t)

copilot-interpreter/CHANGELOG

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
2024-07-07
2+
* Add support for struct field updates. (#520)
3+
14
2024-05-07
25
* Version bump (3.19.1). (#512)
36

copilot-interpreter/src/Copilot/Interpret/Eval.hs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22

33
-- | A tagless interpreter for Copilot specifications.
44

5-
{-# LANGUAGE BangPatterns #-}
6-
{-# LANGUAGE DeriveDataTypeable #-}
7-
{-# LANGUAGE GADTs #-}
8-
{-# LANGUAGE Safe #-}
5+
{-# LANGUAGE BangPatterns #-}
6+
{-# LANGUAGE DeriveDataTypeable #-}
7+
{-# LANGUAGE GADTs #-}
8+
{-# LANGUAGE Safe #-}
9+
{-# LANGUAGE ScopedTypeVariables #-}
910

1011
module Copilot.Interpret.Eval
1112
( Env
@@ -17,8 +18,9 @@ module Copilot.Interpret.Eval
1718

1819
import Copilot.Core (Expr (..), Field (..), Id, Name, Observer (..),
1920
Op1 (..), Op2 (..), Op3 (..), Spec, Stream (..),
20-
Trigger (..), Type (..), UExpr (..), arrayElems,
21-
specObservers, specStreams, specTriggers)
21+
Trigger (..), Type (..), UExpr (..), Value (..),
22+
arrayElems, specObservers, specStreams,
23+
specTriggers, updateField)
2224
import Copilot.Interpret.Error (badUsage)
2325

2426
import Prelude hiding (id)
@@ -244,6 +246,12 @@ evalOp2 op = case op of
244246
BwShiftR _ _ -> ( \ !a !b -> shiftR a $! fromIntegral b )
245247
Index _ -> \xs n -> (arrayElems xs) !! (fromIntegral n)
246248

249+
UpdateField (Struct _) ty (fieldAccessor :: a -> Field s b) ->
250+
\stream fieldValue ->
251+
let newField :: Field s b
252+
newField = Field fieldValue
253+
in updateField stream (Value ty newField)
254+
247255
-- | Apply a function to two numbers, so long as the second one is
248256
-- not zero.
249257
--

copilot-language/CHANGELOG

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
2024-07-06
1+
2024-07-07
22
* Remove deprecated function Copilot.Language.Spec.forall. (#518)
3+
* Add support for struct field updates. (#520)
34

45
2024-05-07
56
* Version bump (3.19.1). (#512)

0 commit comments

Comments
 (0)