lf-lang / lingua-franca

Intuitive concurrent programming in any language
https://www.lf-lang.org
Other
228 stars 62 forks source link

Interleaved connections with hierarchy #2079

Open edwardalee opened 10 months ago

edwardalee commented 10 months ago

In the C target, @cmnrd has pointed out that the way interleaved connections work with hierarchical programs is arguably incorrect. Consider a test program that looks like this (complete code below):

image

The connection is interleaved:

   a.out-> interleaved(b.in)

We add hierarchy as follows (complete code below):

image

where the interleaved connection is as follows:

  a.out -> interleaved(c.in)

Currently, the the behavior is that the first bank member of C will receive the first two outputs from a[0] followed by the first output from a[1]. This is not the same as above and arguably incorrect because the outside connection should not change how the inside connection is interpreted.

The reason for this behavior is that the C target has a single PortInstance that represents all the connections, inside and out, and the interleaving changes the order in which mixed radix digits are placed in the number that is used to index into the array of channels. I believe that the behavior could be made the same as the version without hierarchy by just implicitly treating an inside connection as interleaved if the outside connection is interleaved (note that the inside connection cannot be explicitly interleaved... that is prohibited by the validator).

Source code for the version without hierarchy:

target C {
  timeout: 100ms
}
reactor A {
  output[2] out:int
  state count:int = 0
  timer t(0, 10ms)
  reaction(t) -> out {=
    for(int i = 0; i < 2; i++) {
      lf_set(out[i], self->count++);
    }
  =}
}
reactor B(bank_index:int = 0) {
  input[3] in:int
  reaction(in) {=
    for(int i = 0; i < 3; i++) {
      lf_print("%d Received %d", self->bank_index, in[i]->value);
    }
  =}
}
main reactor {
  a = new[3] A()
  b = new[2] B()
  a.out -> interleaved(b.in)
}

Source code for the version with hierarchy:

target C {
  timeout: 100ms
}
reactor A {
  output[2] out:int
  state count:int = 0
  timer t(0, 10ms)
  reaction(t) -> out {=
    for(int i = 0; i < 2; i++) {
      lf_set(out[i], self->count++);
    }
  =}
}
reactor B(bank_index:int = 0) {
  input[3] in:int
  reaction(in) {=
    for(int i = 0; i < 3; i++) {
      lf_print("%d Received %d", self->bank_index, in[i]->value);
    }
  =}
}
reactor C {
  input[3] in:int
  b = new B()
  in -> b.in
}
main reactor {
  a = new[3] A()
  c = new[2] C()
  a.out -> interleaved(c.in)
}
cmnrd commented 10 months ago

The reason for this behavior is that the C target has a single PortInstance that represents all the connections, inside and out, and the interleaving changes the order in which mixed radix digits are placed in the number that is used to index into the array of channels. I believe that the behavior could be made the same as the version without hierarchy by just implicitly treating an inside connection as interleaved if the outside connection is interleaved (note that the inside connection cannot be explicitly interleaved... that is prohibited by the validator).

I am not sure if this will work reliably in all cases. What if the connection forks off somewhere in the hierarchy? What if there is another connection on the path that uses the interleaved keyword?