Skip to content

Reusability of preprocessing functions to reduce code bloat #297

Closed

Description

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?

Activity

  1. antanas-kalkauskas-sensmetry commented on Feb 28, 2022

    @antanas-kalkauskas-sensmetry
    Author

    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.

  2. ivanperez-keera commented on Feb 28, 2022

    @ivanperez-keera
    Member

    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?)

  3. antanas-kalkauskas-sensmetry commented on Feb 28, 2022

    @antanas-kalkauskas-sensmetry
    Author

    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.

  4. ivanperez-keera commented on Jan 25, 2025

    @ivanperez-keera
    Member

    I have an idea of how we could do this. We need to introduce a construct for named functions of all arities, a way to define named functions and add them to the spec, and a way to compose and combine OP{1,2,3} with each other.

  5. ivanperez-keera commented on Jan 25, 2025

    @ivanperez-keera
    Member

    And I suspect that, if we do that, we might be able to get rid of or re-define the interface of local, and thus be able to close issue #253.

  6. locked and limited conversation to collaborators on Nov 27, 2025
  7. converted this issue into a discussion #701 on Nov 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    feature requestRequest or advice for a featurequestionFurther information is requested

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions