[[combinable]] without [[combine]]

Technical questions regarding the XTC tools and programming with XMOS.
Post Reply
User avatar
aclassifier
Respected Member
Posts: 483
Joined: Wed Apr 25, 2012 8:52 pm
Contact:

[[combinable]] without [[combine]]

Post by aclassifier »

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


--
Øyvind Teig
Trondheim (Norway)
https://www.teigfam.net/oyvind/home/
Gothmag
XCore Addict
Posts: 129
Joined: Wed May 11, 2016 3:50 pm

Post by Gothmag »

[[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: 483
Joined: Wed Apr 25, 2012 8:52 pm
Contact:

Post by aclassifier »

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)
--
Øyvind Teig
Trondheim (Norway)
https://www.teigfam.net/oyvind/home/
Gothmag
XCore Addict
Posts: 129
Joined: Wed May 11, 2016 3:50 pm

Post by Gothmag »

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: 483
Joined: Wed Apr 25, 2012 8:52 pm
Contact:

Post by aclassifier »

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.
--
Øyvind Teig
Trondheim (Norway)
https://www.teigfam.net/oyvind/home/
Gothmag
XCore Addict
Posts: 129
Joined: Wed May 11, 2016 3:50 pm

Post by Gothmag »

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.
Post Reply