Repository navigation
Description
If we have a Copilot specification where we have multiple conditions for triggering the same function (but perhaps we are providing different arguments), then in generated C code we would have multiple declarations of trigger functions and multiple definitions of helper functions and the generated code could not be compiled without modifications.
Example
Suppose we would like to trigger the switchOffPower(..) function if the current of one of the components is too large. We would also like to pass the value of the signal and the index of the component to the switchOffPower(..).
{-# LANGUAGE RebindableSyntax #-}
module RepeatedDeclarationsExample where
import Language.Copilot
import Copilot.Compile.C99
current1 :: Stream Float
current1 = extern "current_signal1" Nothing
current2 :: Stream Float
current2 = extern "current_signal2" Nothing
spec = do
trigger "switchOffPower" (current1 > 0.8) [arg current1, arg (constant 0 :: Stream Word8)]
trigger "switchOffPower" (current2 > 0.8) [arg current2, arg (constant 1 :: Stream Word8)]
main :: IO ()
main = do
reify spec >>= compile "repeated_declarations_example"Then the repeated_declarations_example.h declares switchOffPower(..) twice:
extern float current_signal1;
extern float current_signal2;
void switchOffPower(float switchOffPower_arg0, uint8_t switchOffPower_arg1);
void switchOffPower(float switchOffPower_arg0, uint8_t switchOffPower_arg1);
void step(void);And repeated_declarations_example.c contains multiple definitions of bool switchOffPower_guard(void), float switchOffPower_arg0(void), uint8_t switchOffPower_arg1(void) .
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include "repeated_declarations_example.h"
static float current_signal1_cpy;
static float current_signal2_cpy;
bool switchOffPower_guard(void) {
return (current_signal1_cpy) > ((float)(0.800000011920929));
}
float switchOffPower_arg0(void) {
return current_signal1_cpy;
}
uint8_t switchOffPower_arg1(void) {
return (uint8_t)(0);
}
bool switchOffPower_guard(void) {
return (current_signal2_cpy) > ((float)(0.800000011920929));
}
float switchOffPower_arg0(void) {
return current_signal2_cpy;
}
uint8_t switchOffPower_arg1(void) {
return (uint8_t)(1);
}
void step(void) {
(current_signal1_cpy) = (current_signal1);
(current_signal2_cpy) = (current_signal2);
if ((switchOffPower_guard)()) {
{(switchOffPower)(((switchOffPower_arg0)()), ((switchOffPower_arg1)()));}
};
if ((switchOffPower_guard)()) {
{(switchOffPower)(((switchOffPower_arg0)()), ((switchOffPower_arg1)()));}
};
}
Activity
ivanperez-keera commentedon Feb 28, 2022
Indeed, that is an error in the spec.
At present, we consider it an error whose detection is delegated on the C compiler.
If you consider the process of compilation as indivisible (Haskell -> running -> GCC), then the error is being caught. What happens is that it is not caught in the first stage, it is caught in the third (when you try to compile the C code).
ivanperez-keera commentedon Feb 28, 2022
I take that back. In principle, it should be an error only if the arguments were different, but not if they are the same.
ivanperez-keera commentedon Aug 15, 2023
I took a quick stab at this. Avoiding multiple declarations of the handler functions if they have the same arguments is easy to do. Avoiding multiple declarations of the arguments or argument generation functions is a bit trickier.
There are two potentially orthogonal improvements to add to Copilot: 1) avoid multiple declarations of the same generation functions (this connects to #297), and 2) use different names for arguments to the same handling function when they come from different triggers with the same handling function name.
ivanperez-keera commentedon Jan 3, 2024
I tried this again and I have a crude implementation that seems to work. There's an architecturally cleaner way of structuring the code (that's way I mean by crude).
I don't want to not rush putting this in the Jan 7, 2024 release; I'd rather discuss it well before it's merged.
We'll talk about it next week during our next planning meeting. If we can, we'll add this to Copilot 3.19, to be released on March 7, 2024.
I'll keep you updated.
ivanperez-keera commentedon Jan 8, 2024
We just had our planning meeting and this issue is likely to be included in the next release. You should see an update on this in a few days.
ivanperez-keera commentedon Nov 11, 2024
Description
The current implementation of Copilot does not allow using the same trigger handler name multiple times, since the C99 backend produces multiple repeated declarations, which is invalid.
So long as the types do not change, or so long as the target language allows for method/function overloading, then the Copilot spec should accept them.
Type
Additional context
None.
Requester
Method to check presence of bug
Not applicable (not a bug).
Expected result
Copilot accepts specs where there are two triggers with the same name, so long as the types of the arguments (and their arity) is the same.
Desired result
Copilot accepts specs where there are two triggers with the same name, so long as the types of the arguments (and their arity) is the same.
Proposed solution
Modify
copilot-c99to accept specs with multiple triggers with the same handler name, and either use the same declaration for both (without repeating it), or produce multiple declarations with different names.Further notes
None.
[-]Repeated declarations of functions in generated C code if a function is triggered twice in specification[/-][+]`copilot-c99`: Allow using same trigger name in multiple declarations[/+]fdedden commentedon Nov 14, 2024
Supporting multiple calls to the same trigger has (in my opinion) one major drawback. In Copilot we basically consider triggers to be the output of a program. Having multiple triggers call the same function means that we have a single output referred from multiple places. This can make the code less readable, and is up to the user to structure it a nice way.
On the other hand, I am pretty sure that allowing multiple definitions do not change the semantics of Copilot anyway (under the limitation that we only allow reuse of the trigger with same arity and argument types). For example: (type annotations omitted for brevity)
could be written in the current version of Copilot as:
Now, I think we can all agree that this approach is much more error-prone, so it might not be a bad idea to support multiple triggers.
I think it is very reasonable to only support multiple triggers if the arity and argument types match exactly. C for example disallows / does not support overloading functions (for good reason, in my opinion). If we disallow it, I think it would be nice to have Copilot check this, instead of relying on the backend to do this. We can make our error message a lot more informative, especially because I expect it is fairly easy for the user to make a mistake/typo where the types do not match.
ivanperez-keera commentedon Nov 14, 2024
Agreed. I think we can proceed with this.
26 remaining items
copilot-c99: Disallow conflicting trigger definitions. Refs Copilot-L…
copilot-c99: Document changes in CHANGELOG. Refs Copilot-Language#296.
copilot-c99: Add specific `Trigger` representation. Refs Copilot-Lang…
copilot-c99: Adapt `mkStep` to use unique triggers. Refs Copilot-Lang…
copilot-c99: Adapt top-level compilation functions to use unique trig…
copilot-c99: Remove duplicate trigger declarations in .h output. Refs C…
copilot-c99: Disallow conflicting trigger definitions. Refs Copilot-L…
copilot-c99: Document changes in CHANGELOG. Refs Copilot-Language#296.
ivanperez-keera commentedon Jan 3, 2025
Change Manager: Verified that:
Build log: https://app.travis-ci.com/github/Copilot-Language/copilot/builds/273723103
The following dockerfile checks that a spec with the same trigger name used multiple times can be compiled correctly, and the resulting C code also compiles, after which it prints the message "Success":
All new top-level definitions have haddock comments, and any sizeable new local definitions or substantial changes have associated comments.
No updates needed.
Bump not needed; API remains backwards compatible.
ivanperez-keera commentedon Jan 3, 2025
Change Manager: Implementation ready to be merged.
Merge remote-tracking branch 'fdedden/develop-multiple-triggers'. Close
Allow multiple triggers with the same name. Refs #30.