[[combinable]] without [[combine]]

Technical questions regarding the xTIMEcomposer, xSOFTip Explorer and Programming with XMOS.
User avatar
aclassifier
Respected Member
Posts: 332
Joined: Wed Apr 25, 2012 8:52 pm

[[combinable]] without [[combine]]

Postby aclassifier » Sat Jun 16, 2018 10:03 am

Here is "eat", a "[[combinable]]" function that's not required to be combinable. According to [2] (also seen on code)
If a function complies to this format then it can be marked as combinable by adding the combinable attribute
My question is, since "[[combinable]]" is possible without "[[combine]]" but "[[combine]]" requires "[[combinable]]" then "[[combinable]]" only is a decoration with no meaning? Nothing is combined with "[[combinable]]" only?

If so, should this be warned about by the compiler? I was made aware of "[[combine]]" only recently (and then as a substitute for placed par), my eyes have read about "[[combine]]" (in [2]) but it never fastened in my gray cells. For me, at least, I would in this case have needed that warning.

Or is "[[combinable]]" by itself (to tell that it certainly is combinable) considered an important decoration that it stands by itself? Like used in a library?

Code: Select all

// Based on code by XMOS [1], expanded with a chan

#include <platform.h>
#include <stdio.h>

interface ifa {
    void send (int x);
};

[[combinable]]
 void ser (server interface ifa i) {
    while (1) {
        select {
            case i.send(int x): {
                printf("%d\n", x);
                break;
            }
        }
    }
}

[[combinable]]
 void cli (client interface ifa i, chanend x) {
    timer t;
    int s;
    int first = 1;
    t :> s;
    while (1) {
        select {
            case first => t when timerafter(s) :> void: {
                i.send(123);
                x <: 123;
                first = 0;
                break;
            }
        }
    }
}

// From [2]
//    A combinable function must obey the following restrictions:
//    * The function must have void return type.
//    * The last statement of the function must be a while(1) statement containing
//      a single select statement.
//    If a function complies to this format then it can be marked as combinable by adding
//    the combinable attribute:
[[combinable]] // Not needed, makes no difference
void eat (chanend x[2]) {
    int val;
    while (1) {
        select {
            case x[int i] :> val: {
                break;
            }
        }
        // val++; // Not legal in [[combinable]]
    }
}

int main (void) {
    interface ifa i[2];
    chan x[2];

    par {
        eat(x); // Cannot be used within the [[combine]] below. Both tasks cannot be combined

        // Using the combine attribute instead of core placement
        // Under the assumption that there isn't an explicit initial handshake, and guarded functions aren't used
        [[combine]] // With this: one core, one timer and one chanend less
        par {
            cli (i[0],x[0]);
            cli (i[1],x[1]);
        }
        [[combine]] // With this: one core and one timer less
        par (int j = 0; j < 2; j++) {
            ser (i[j]);
        }
    }
    return 0;
}
[1] Replicated par and placements
[2] XMOS Programming Guide
Gothmag
Experienced Member
Posts: 127
Joined: Wed May 11, 2016 3:50 pm

Postby Gothmag » Sun Jun 17, 2018 5:08 pm

[[combine]] is not the only way to achieve that. If you explicitly place tasks on a core they combine. If they're not [[combinable]] you get an error. Just remember alot of it(xC) is to keep you from making easily preventable mistakes by forcing you to be more explicit.

Code: Select all

on tile[0].core[0]: myTask();
on tile[0].core[0]: myOtherTask();
User avatar
aclassifier
Respected Member
Posts: 332
Joined: Wed Apr 25, 2012 8:52 pm

Postby aclassifier » Sun Jun 17, 2018 5:58 pm

Thanks! Explicitly placing them is what I have always done but for a day or two I have been in doubt whether that was enough! (The doubt was introduced by the code I posted above).

But now I have regained confidence. Here is some code (excuse me, it's rather out of context. But it does display the points) showing two cases. By the way: the memory used is not 100% equal..:

Code: Select all

int main() {

    spi_master_if i_spi[NUM_SPI_CLIENT_USERS];
    radio_if_t    i_radio;
    irq_if_t      i_irq;

    #if (DO_PLACED == 0)
        par {
            on tile[0].core[0]: spi_master_2 (i_spi, NUM_SPI_CLIENT_USERS, p_sclk, p_mosi, p_miso, SPI_CLOCK, p_spi_cs_en, maskof_spi_and_probe_pins, NUM_SPI_CS_SETS);
            on tile[0].core[0]: RFM69_driver (i_radio, p_spi_aux, i_spi[SPI_CLIENT_0], SPI_CLIENT_0);
            on tile[0].core[0]: RFM69_client (i_irq, i_radio);
            on tile[0].core[0]: IRQ_detect_task (i_irq, p_spi_irq, p_probe4);
        }
        /*
        Constraint check for tile[0]:
        Cores available:            8,   used:          1 .  OKAY
        Timers available:          10,   used:          1 .  OKAY
        Chanends available:        32,   used:          0 .  OKAY
        Memory available:       65536,   used:      18100 .  OKAY
          (Stack: 1172, Code: 15354, Data: 1574)
        Constraints checks PASSED. 
        */
    #elif (DO_PLACED == 1)
        [[combine]]
        par {
            spi_master_2 (i_spi, NUM_SPI_CLIENT_USERS, p_sclk, p_mosi, p_miso, SPI_CLOCK, p_spi_cs_en, maskof_spi_and_probe_pins, NUM_SPI_CS_SETS);
            RFM69_driver (i_radio, p_spi_aux, i_spi[SPI_CLIENT_0], SPI_CLIENT_0);
            RFM69_client (i_irq, i_radio);
            IRQ_detect_task (i_irq, p_spi_irq, p_probe4);
        }
        /*
        Constraint check for tile[0]:
          Cores available:            8,   used:          1 .  OKAY
          Timers available:          10,   used:          1 .  OKAY
          Chanends available:        32,   used:          0 .  OKAY
          Memory available:       65536,   used:      18024 .  OKAY
            (Stack: 1168, Code: 15288, Data: 1568)
        Constraints checks PASSED.
        */
    #endif
    return 0;
}
Here is a version from earlier today, when communication with IRQ_detect_task was over a channel. All tasks are [[combinable]], therefore both chanends could not be placed on the same core, to avoid "error: `c_irq_rising' used between two combined tasks". So IRQ_detect_task had to go on core[1], wheras all the others are on core[0]. So chan has a price. And the that I can juggle with [[combinable]], [[combine]], placed par and channels or not is new to me. I have juggled, but less "controllable". I mean, how chan enters the picture:

Code: Select all

int main() {

    spi_master_if i_spi[NUM_SPI_CLIENT_USERS];
    radio_if_t      i_radio;
    chan             c_irq_rising;

    par {
        on tile[0].core[0]: spi_master_2 (i_spi, NUM_SPI_CLIENT_USERS, p_sclk, p_mosi, p_miso, SPI_CLOCK, p_spi_cs_en, maskof_spi_and_probe_pins, NUM_SPI_CS_SETS);
        on tile[0].core[0]: RFM69_driver (i_radio, p_spi_aux, i_spi[SPI_CLIENT_0], SPI_CLIENT_0);
        on tile[0].core[0]: RFM69_client (c_irq_rising, i_radio);
        on tile[0].core[1]: IRQ_detect_task (c_irq_rising, p_spi_irq, p_probe4);
    }
    /*    
    Constraint check for tile[0]:
      Cores available:            8,   used:          2 .  OKAY
      Timers available:          10,   used:          2 .  OKAY
      Chanends available:        32,   used:          2 .  OKAY
      Memory available:       65536,   used:      18100 .  OKAY
        (Stack: 1228, Code: 15262, Data: 1610)
    Constraints checks PASSED.
    */
    return 0;
}
I guess it's rather difficult to know when a [[combinable]] is "used". There has to be some semantic knowledge by the coder. Anybody having a check or rules list? (If not I'll try to make one later on)
Gothmag
Experienced Member
Posts: 127
Joined: Wed May 11, 2016 3:50 pm

Postby Gothmag » Sun Jun 17, 2018 6:35 pm

Well it makes sense that a chan wouldn't want to connect to itself on the same core because both chanends would point to the same actual context since combinable is literal in the combining. It should have worked to just move to a seperate core as well. I think rules for when combined is used is not specifically needed or useful. You combine until the timing is no good or when it's logical to do so. If it's all synchronous there is no reason you can't do it on one core, you're wasting 80% of your CPU time either way. The compiler will likely be set to -Os so it'll optimize to those params when automatically placed. As to the memory difference by letting the compiler handle more of the optimizations it's able to reduce code size so that's not terribly surprising.
User avatar
aclassifier
Respected Member
Posts: 332
Joined: Wed Apr 25, 2012 8:52 pm

Postby aclassifier » Sun Jun 17, 2018 7:17 pm

I thought that using a an unbuffered, synchronous chan would deadlock if both chanends were connected to the same combined select on a core!? So the rule and error message do a deadlock avoidance by design/pattern?
It should have worked to just move to a seperate core as well.
You mean like I did in the last code example? The sending chanend on core[1] and the receiving chanend with the combined select on core[0]?

I have used -O2 in the examples above.
Gothmag
Experienced Member
Posts: 127
Joined: Wed May 11, 2016 3:50 pm

Postby Gothmag » Sun Jun 17, 2018 8:59 pm

aclassifier wrote:I thought that using a an unbuffered, synchronous chan would deadlock if both chanends were connected to the same combined select on a core!? So the rule and error message do a deadlock avoidance by design/pattern?
It should have worked to just move to a seperate core as well.
You mean like I did in the last code example? The sending chanend on core[1] and the receiving chanend with the combined select on core[0]?

I have used -O2 in the examples above.
Haha, yes just like you did. Sorry, travelling so didn't see clearly, I thought you switched tiles. Yes, I believe the error is to avoid a guaranteed exception.

Who is online

Users browsing this forum: No registered users and 5 guests