lingua-franca icon indicating copy to clipboard operation
lingua-franca copied to clipboard

Surprising behavior involving a reactor that splits a multiport into individual ports

Open petervdonovan opened this issue 2 years ago • 6 comments

I'm labeling this as a question because it's possible this stems from a misunderstanding on my part of the semantics of multiports. I had difficulty getting any programs similar to these to work.

IndexIntoMultiportOutput

This program compiles successfully and executes with the C target, but its behavior is inconsistent with my expectations. I expect it to print "1 = 1" and "2 = 2", but instead, it prints "1 = 0" and "2 = 1". This is because instead of connecting the ith reaction to the ith multiport channel, it fills reactions into the multiport channels starting at channel 0. I find this kind of behavior strange and hard to reason about. The way messages are routed inside MultiportSplitter should not be affected by how things are connected outside of it.

Screenshot from 2022-08-06 18-39-01

This is the output:

---- Start execution at time Sat Aug  6 18:37:04 2022
---- plus 231711826 nanoseconds.
---- Using 1 workers.
1 = 0
2 = 1
---- Elapsed logical time (in nsec): 0
---- Elapsed physical time (in nsec): 208,243

This is the program:

target C

reactor ReactorWithMultiport(width: int(3)) {
    output[width] out: int

    reaction(startup) -> out {=
        for (int i = 0; i < self->width; i++) {
            lf_set(out[i], i);
        }
    =}
}

reactor MultiportSplitter(width: int(3)) {
    input[width] in: int

    output out0: int
    output out1: int
    output out2: int

    in -> out0, out1, out2
}

main reactor IndexIntoMultiportOutput {
    source = new ReactorWithMultiport()
    splitter = new MultiportSplitter()

    source.out -> splitter.in

    reaction(splitter.out1) {=
        printf("1 = %d\n", splitter.out1->value);
    =}
    reaction(splitter.out2) {=
        printf("2 = %d\n", splitter.out2->value);
    =}
}

IndexIntoMultiportInput

This program fails with the C++ target, I think during the assemble phase of execution, and I do not understand why. index-into-multiport-input This is the output of the program:

terminate called after throwing an instance of 'std::runtime_error'
  what():  unexpected case
Aborted (core dumped)

This is the program:

target Cpp

reactor ReactorWithMultiport {
    input[3] in: int

    reaction(in) {=
        for (auto i = 0; i < in.size(); i++) {
            if (in[i].is_present()) {
                std::cout << "received input at port " << i << std::endl;
            }
        }
    =}
}

reactor MultiportSplitter {
    output[3] out: int

    input in0: int
    input in1: int
    input in2: int

    in0, in1, in2 -> out
}

main reactor IndexIntoMultiportInput {
    splitter = new MultiportSplitter()
    receiver = new ReactorWithMultiport()

    splitter.out -> receiver.in

    reaction(startup) -> splitter.in1 {=
        splitter.in1.set(42);
    =}
}

petervdonovan avatar Aug 07 '22 01:08 petervdonovan

The first example indeed looks like a bug in the C code generator. Have you tried this in the C++ target?

The error message from the C++ runtime in the second example is not very informative, but it is saying that directly connecting inputs to outputs is not implemented (as I did not consider this a valid use at the point of writing the code). As it looks, we do support direct connections in LF, so we should update the C++ runtime. In the meantime, you can work around this by adding a reaction in the middle that dimply forwards all values from the inputs to the correct output port.

cmnrd avatar Sep 07 '22 07:09 cmnrd

The first example indeed looks like a bug in the C code generator. Have you tried this in the C++ target?

I tried it in C++ just now and saw the same error as in the other example:

terminate called after throwing an instance of 'std::runtime_error'
  what():  unexpected case
Aborted (core dumped)

Incidentally, I was surprised that I needed to call .get() twice, although I suppose it makes sense since you have to get the port and then the value of the port. Here is the C++ version of the program:

target Cpp

reactor ReactorWithMultiport(width: int(3)) {
    output[width] out: int

    reaction(startup) -> out {=
        for (int i = 0; i < width; i++) {
            out[i].set(i);
        }
    =}
}

reactor MultiportSplitter(width: int(3)) {
    input[width] in: int

    output out0: int
    output out1: int
    output out2: int

    in -> out0, out1, out2
}

main reactor {
    source = new ReactorWithMultiport()
    splitter = new MultiportSplitter()

    source.out -> splitter.in

    reaction(splitter.out1) {=
        std::cout << "1 = " << splitter.out1.get().get() << std::endl;
    =}
    reaction(splitter.out2) {=
        std::cout << "2 = " << splitter.out2.get().get() << std::endl;
    =}
}

petervdonovan avatar Sep 07 '22 18:09 petervdonovan

Oh, right this is the same problem as in the other example.

In the C++ target, get() on a port always returns a smart pointer to the value. You can retrieve the actual value again with get() as you did or you can use the C++ dereference operator * (this is what I usually use).

cmnrd avatar Sep 08 '22 08:09 cmnrd

I fixed the problem in the C++ runtime in https://github.com/lf-lang/lingua-franca/pull/1361. Now the first program works as expected in C++.

cmnrd avatar Sep 08 '22 09:09 cmnrd

Looks like this still has to be fixed for C?

lhstrh avatar Sep 28 '22 08:09 lhstrh

Yes, even after #1370 this issue seems to persist.

petervdonovan avatar Sep 28 '22 15:09 petervdonovan