Copilot-Language / copilot

A stream-based runtime-verification framework for generating hard real-time C code.
http://copilot-language.github.io
633 stars 50 forks source link

Reusability of preprocessing functions to reduce code bloat #297

Open antanas-kalkauskas-sensmetry opened 2 years ago

antanas-kalkauskas-sensmetry commented 2 years ago

If we have a stream A in a copilot specification that is defined by applying some preprocessing to another stream B (say an external signal), it seems that in the generated code the calculation of stream A would implement the whole preprocessing to calculate it from stream B. But this means that if there is some involved preprocessing function that is commonly used in the specification (i. e. some filtering function or a function to define state transitions in a large state machine) then the preprocessing code would be reimplemented again many times which could lead to bloated .text section in generated binary.

If there is a commonly used preprocessing function it would be good to have it defined once and to be reused in the constructions of the streams in generated C code. Is there some way to do that?

antanas-kalkauskas-sensmetry commented 2 years ago

Example

A quick example to illustrate the issue. Say we have three signals to which we apply the same preprocessing function:

{-# LANGUAGE RebindableSyntax #-}

module PreprocessingReimplemented where
import Language.Copilot
import Copilot.Compile.C99

signal1 :: Stream Float 
signal1 = extern "signal1" Nothing 

signal2 :: Stream Float 
signal2 = extern "signal2" Nothing

signal3 :: Stream Float 
signal3 = extern "signal3" Nothing

-- some example filter constants
a = 1
b = 2
c = 3

-- just an example of preprocessing, this can be much more complicated
involvedPreprocessing :: Stream Float -> Stream Float
involvedPreprocessing x = y
    where y = a*x + b*([0]++y) + c*([0, 0]++y)

spec = do
    trigger "triggerFunction1" (involvedPreprocessing signal1 > 10) [arg (involvedPreprocessing signal1)]
    trigger "triggerFunction2" (involvedPreprocessing signal2 > 10) [arg (involvedPreprocessing signal2)]
    trigger "triggerFunction3" (involvedPreprocessing signal3 > 10) [arg (involvedPreprocessing signal3)]

main :: IO ()
main = do
    reify spec >>= compile "preprocessing_reimplemented"

If we look in the generated C code we would see that the filter functions are repeatedly implemented 3 times:

...

bool triggerFunction1_guard(void) {
  return (((signal1_cpy) + (((float)(2.0)) * ((s0_get)((0))))) + (((float)(3.0)) * ((s1_get)((0))))) > ((float)(10.0));
}

float triggerFunction1_arg0(void) {
  return ((signal1_cpy) + (((float)(2.0)) * ((s2_get)((0))))) + (((float)(3.0)) * ((s3_get)((0))));
}

bool triggerFunction2_guard(void) {
  return (((signal2_cpy) + (((float)(2.0)) * ((s4_get)((0))))) + (((float)(3.0)) * ((s5_get)((0))))) > ((float)(10.0));
}

float triggerFunction2_arg0(void) {
  return ((signal2_cpy) + (((float)(2.0)) * ((s6_get)((0))))) + (((float)(3.0)) * ((s7_get)((0))));
}

bool triggerFunction3_guard(void) {
  return (((signal3_cpy) + (((float)(2.0)) * ((s8_get)((0))))) + (((float)(3.0)) * ((s9_get)((0))))) > ((float)(10.0));
}

float triggerFunction3_arg0(void) {
  return ((signal3_cpy) + (((float)(2.0)) * ((s10_get)((0))))) + (((float)(3.0)) * ((s11_get)((0))));

...

In this case the code bloat is not significant, but this is just an example to illustrate the issue.

ivanperez-keera commented 2 years ago

At present, there is no way to define these kinds of re-usable functions in Copilot. It is a good idea to have them, but it's not in our immediate plans because we are working on other features.

The only way possible as far as I can think is to rely on existing optimizers for C code that could factorize that out.

That being said: is the size of the .text sections being generated a serious issue? (I see how it could potentially be, but is there a real-world problem that needed solving and you could not use copilot for for this reason?)

antanas-kalkauskas-sensmetry commented 2 years ago

We are evaluating using Copilot in a scenario where there is significant function reuse for many input signals. In our initial explorations, the increase of ROM relative to a reasonable handwritten code would be a factor of 10 (if not more) and this makes it infeasible to use Copilot code in an embedded system.