boschresearch / blech

Blech is a language for developing reactive, real-time critical embedded software.
Apache License 2.0
72 stars 6 forks source link

read only global variable #69

Closed MohamedGhardallou closed 5 days ago

MohamedGhardallou commented 2 years ago

Hello

Is your feature request related to a problem? Please describe.

Imagine you have a button. the input signal needs to be filtered before using it in the application. without global variables , you will need to pass "down" this variable to every activity.

Describe the solution you'd like

I would like to be able to create an activity that produces a global variable. this variable will be written only by this activity but used by any other activity that needs it.

Describe alternatives you've considered we could try to define external "get" c function but then i want my logic to be written in blech.

Related why not also permit external input global variable ?

Thank you

FriedrichGretz commented 2 years ago

Hi, your suggestion contradicts the design choice to have no global variables in Blech. Here is a short example that explains why this would be a problem:

global x // to be written only by Filter, and read by all others

// ... some defitions of activities

activity Example()()
    cobegin
        run Filter()() // reads raw value, writes filtered value to global x
    with
        run Consumer()()
    end
end

Above would be the architecture that you propose. Notice however that x is not part of the API of the activities but used only implicitly. This introduces two problems:

  1. Readability: a colleague who needs to read and understand the code must know the entire program to know that there is a shared variable between two activities that otherwise appear to be unrelated.
  2. Compilation: causality dictates that a shared variable is first written and then read within a tick. The compiler (as it is now) relies on the parameter lists to find out relationships between activities. In the example above however we would need a global analysis that looks inside the code of the activities to find out that x is shared. This precludes separate compilation of activities and thereby breaks the module system.

A solution to your problem might be to reconsider the design of your code. Note, that you do not have to read-in and filter the raw button value at the entry point activity. Instead you can use external inputs (https://www.blech-lang.org/docs/user-manual/declarations/#local-external-variables) in your Filter activity and then only the produced filtered value needs to be propagated. The question is then to how many different activities is it propagated and how deeply are they nested? In our experience so far, this was a negligible overhead. I hope this helps?

MohamedGhardallou commented 2 years ago

Hello, can't both readability and compilation issue be solved if we explicitly declare global variables inside activities that use them ?

for example this is sketch of my application:

activity filter()(start_btn : bool , limit_switch: bool )
    @[CInput (binding = "start_btn", header = "board_io.h")]
    extern let raw_start_btn: bool
    @[CInput (binding = "limit_switch", header = "board_io.h")]
    extern let raw_limit_switch: bool
    cobegin
        run FilterSignal(raw_start_btn)(start_btn)
    with
        run FilterSignal(raw_limit_switch)(limit_switch)
    end
end
activity my_app(start_btn : bool , limit_switch: bool )()
    repeat
        let choice : nat8 = run voice_menu(start_btn)()
        if choice == 1 then
            run cycle_a(start_btn , limit_switch)()
        else 
            run cycle_b(start_btn , limit_switch)()
        end
    end
end

@[EntryPoint]
activity Ctrl()()   
    var start_btn : bool = false 
    var limit_switch: bool = false
    cobegin
        run filter()(start_btn,limit_switch)
    with
        my_app(start_btn,limit_switch)()
    end
end

if we use global input variables , i imagine something like this

activity filter()()
    @[CInput (binding = "start_btn", header = "board_io.h")]
    extern let raw_start_btn: bool
    @[CInput (binding = "limit_switch", header = "board_io.h")]
    extern let raw_limit_switch: bool

    export global start_btn : bool
    export global limit_switch : bool

    cobegin
        run FilterSignal(raw_start_btn)(start_btn)
    with
        run FilterSignal(raw_limit_switch)(limit_switch)
    end
end
activity cycle_a()()
    import global start_btn : bool
    import global stop_btn : bool
    // do something here
end
activity my_app()() 
    repeat
        let choice : nat8 = run voice_menu()()
        if choice == 1 then
            run motor_cycle_1()()
        else 
            run motor_cycle_2()()
        end
    end
end

@[EntryPoint]
activity Ctrl()()   
    cobegin
        run filter()()
    with
        my_app()()
    end
end

for my use case , i managed to have 4 nested level. I agree that this is manageable but maybe this can be addressed at the langage level without breaking blech clarity and simplicity.

schorg commented 2 years ago

This introduces two problems:

  1. Readability: ...
  2. Compilation: ...

Not to forget the third Problem

  1. Reuse: You cannot simply run an activity that uses a global from two different, concurrent run-calls in your application. Because both calls implicitly interact via the global variable.

Activities using globals are singletons in our terminology, for which we take care in the compiler if they use external variables see: https://www.blech-lang.org/docs/language-evolution/blech-c-interface/env-variables/

MohamedGhardallou commented 2 years ago

Hello,

You cannot simply run an activity that uses a global from two different, concurrent run-calls in your application. Because both calls implicitly interact via the global variable

So i think that my statement was not clear. I am talking about "global input variable".

What i want to have is : a "singleton activity" that exclusively writes to a global variable and other "non singleton activities" that can only read this variable.

But even before talking about that , let's consider that i have a sensor value in my c program. and i want to make this variable available to every activity in my blech program ( without the need to pass it down from the entry activity )

What's the harm in declaring this variable as global input variable like this

@[CInput (binding = "theSensor", header = "sensors.h")]
extern let sensor: int8 

activity cycle_a()()
    // do something here with sensor
end

@[EntryPoint]
activity Ctrl()()   
      // do something here with sensor
      run cycle_a()()
      // ....
end

Sensor here can be considered as "module input variable" and it is updated each cycle.

As a side note we may also need to think about how to declare module input variable maybe we should not reference c header files directly to make module more reusable.

schorg commented 2 years ago

Nobody prevents you from using two or more extern let that point to the same C variable. Blech will copy-in the value within each tick.

activity cycle_a()()
     @[CInput (binding = "theSensor", header = "sensors.h")]
     extern let sensor: int8 
    // do something here with sensor
end

@[EntryPoint]
activity Ctrl()()   
      @[CInput (binding = "theSensor", header = "sensors.h")]
      extern let sensor: int8 
      // do something here with sensor
      run cycle_a()()
      // ....
end

It is a "trick", but it should work