About function pointers

Dear remodeling IoTlers!

Ludwig just made me aware that Joakim's PR for NVRAM [1] introduces function pointers as part of the device driver struct. This made me remember that there were already similar function pointers introduced as part of netdev.

As I was always opposed to use function pointers in that way, I could not recall what was the reason to use them for netdev. I don't like this pseudo object oriented C style (dev->write() and the like) because we made bad experiences with it at my former company. While writing the whole HAL for our firmware there using function pointers this way, made programming pretty convenient, it turned out to become problematic speed-wise and was a nightmare to debug.

Hence, I would like to trigger a fundamental discussion on this topic. Does anyone has a particular opinion on the topic why function pointer based device drivers should be a good or a bad idea? And can anyone explain to me the need of function pointers for netdev?

Cheers, Oleg

[1] https://github.com/RIOT-OS/RIOT/pull/2353

Oleg, all,

Just as one data point, modern compilers are able to optimise out function pointers if they are constant. That is, if the struct containing the function pointer is a const struct, in a typical case "static const", then e.g. LLVM is able to optimise the function pointer call into the resolved function call, even during linking time. I'm not sure if GCC can do that in link time, but if the struct is defined as a static struct in a header file, it is able to optimise out the struct, including any function pointers in it.

Of course, some care is needed to make sure that the compiler does the right thing. It is relatively easy to break the pattern.

--Pekka

Hello,

I began to use function pointers in the 'radio_driver.h' when trying to create a unified model for radio drivers. I guess it went over later to the whole netdev effort.

My idea then was to have a "fast", predictable call chain from the MAC layer to the physical radio transceiver functions: most MAC protocols expect to run with precise delays, and a mechanism relying on message between threads (like 'transceiver') did not fit that need. The model of driver as structs containing function pointers is the one used in Contiki: it seemed quite a natural way to handle the problem to me... In their code, the needed drivers are aliased as 'NETSTACK_xxx' macros, then functions are called by invoking 'NETSTACK_xxx.func()'. I've never seen advanced dereferencing (->) nor object-oriented like constructs in that context.

As P. Nikander said, since you are supposed to know your hardware at compile time, these pointers are actually constants that should be resolved at compile (linking) time -- even with GCC. Theoretically, it should be translated by standard indirect calls at the assembler level. Could you tell us more about your previous experiments? It seems quite strange to me that such a mechanism (that seems simple to me) could impose speed and debugging penalties.

The other question is how could we implement the network stack? Using a mechanism like 'transceiver', that relies on message-passing between threads, implies that there is no predictable delay for executing network functions (one has to wait for the scheduler to switch to the thread(s) that execute(s) the feature(s) you want to use). At some levels -- like between MAC and PHY -- this can be a huge disadvantage.

Is there a better way to obtain deterministic/direct API calls than using the "struct of function pointers" scheme ?...

Regards,

Le 18/02/2015 11:38, Oleg Hahm a �crit :

Hi All,

We use this approach exhaustively (for drivers and practically everything else) with the same result. The struct does not even need to be declared as a const struct if it is used in a way that implies such, though it is definitely more elegant to declare it so.

Though I do have a preference for binding prerequisite functions as a part of the interface, rather than hiding them in the body of the code with macros.

As a disclaimer, I haven’t actually checked the result of this particular style of binding, which in our application does use a dereference to access the function pointer. I will have a shot at that later on. Our decision was that effective modularity was more critical than performance for our driver elements (at least until we experience a performance issue).

ie. //Initialise the radio device extern int8_t SI446x_init(struct si446x_s *device, transfer_data_function spi_transfer_function, set_pin_function set_sdn, get_pin_function get_cts, get_pin_function get_irq, get_pin_function get_cca); Where for the purposes of this example the above are defined so: `

typedef int8_t (transfer_data_function)(void context, uint8_t *data, uint8_t size); typedef int8_t (set_pin_function)(void context, uint8_t value); typedef int8_t (get_pin_function)(void context);

`

This means that any dependencies are explicitly defined in the interface, rather than hidden in the source code. It also means that instantiating multiple devices and linking them to different underlying communication channels becomes possible, and you can do some quite neat testing based on that.

In my opinion this is a cleaner abstraction, and leads to clearer parallel composition (or wiring) of modules, rather than the oft used vertical composition. I had a bit of a wild rant about this recently, and there is a /very/ draft set of slides if anyone is interested.

Hope some of this is valuable,

Ryan

Hi Pekka,

thanks for your input.

Of course, some care is needed to make sure that the compiler does the right thing. It is relatively easy to break the pattern.

I see your point, but actually, I don't want to build a whole system model based on any assumption about what the compiler might optimize. Without testing I'm pretty sure that you're right and an up-to-date version of GCC is able to optimize static function pointers, but we don't know which weird compilers on some esoteric hardware platform might be used for RIOT somewhere sometime, so I rather rely on optimized C code than on optimized compilers.

Cheers, Oleg

Hi Kévin!

I began to use function pointers in the 'radio_driver.h' when trying to create a unified model for radio drivers. I guess it went over later to the whole netdev effort.

Yes, I remember that, but why can't this be implemented in a similar way we did for most of the peripheral drivers like SPI? Instead of calling a function pointer, we simply pass a struct describing the low-level device (driver) that should be used.

My idea then was to have a "fast", predictable call chain from the MAC layer to the physical radio transceiver functions: most MAC protocols expect to run with precise delays, and a mechanism relying on message between threads (like 'transceiver') did not fit that need.

I completely agree on the point that we need to have direct function calls for MAC protocols (see also [1502.01968] Old Wine in New Skins? Revisiting the Software Architecture for IP Network Stacks on Constrained IoT Devices).

The model of driver as structs containing function pointers is the one used in Contiki: it seemed quite a natural way to handle the problem to me... In their code, the needed drivers are aliased as 'NETSTACK_xxx' macros, then functions are called by invoking 'NETSTACK_xxx.func()'.

I think it is indeed one of the two natural solutions - the other one would be MACROing everything. I made experiences with both versions while developing on firmware for older sensor board platforms. And both had their drawbacks. Thus, when I started to work on what now is known as RIOT, I wanted to avoid Macros and a function pointer based abstraction layer model as much as possible. Of course, there are cases where either the one or the other will be the best solution, but I think we have to make sure to check if there are no alternative solutions that might work better on the long run.

As P. Nikander said, since you are supposed to know your hardware at compile time, these pointers are actually constants that should be resolved at compile (linking) time -- even with GCC. Theoretically, it should be translated by standard indirect calls at the assembler level.

You name it: If everything is known at link time, then there is also always a way around function pointers. As I've written in the previous mail: I'd rather tell the compiler (or linker) how to do the stuff in an efficient way than hoping for the toolchain being smarter than me.

Could you tell us more about your previous experiments? It seems quite strange to me that such a mechanism (that seems simple to me) could impose speed and debugging penalties.

I cannot disclose any code here, because it was proprietary code for my old company, but the problem was a layered one. We had a highly abstracted driver model, similar to Linux' one with char and block devices, abstracted serial line devices and the like. Thus, an application or any high level code accessing low-level drivers would be de-referenced multiple times. This made it incredible hard to debug (because you had to follow all the pointer de-referencing) and made it almost impossible to hold some strict deadlines for TDMA on our old MSP430F1612.

The other question is how could we implement the network stack? Using a mechanism like 'transceiver', that relies on message-passing between threads, implies that there is no predictable delay for executing network functions (one has to wait for the scheduler to switch to the thread(s) that execute(s) the feature(s) you want to use). At some levels -- like between MAC and PHY -- this can be a huge disadvantage.

I think we can live with the overhead of IPC calls on all layers except the MAC protocol. However, even message-passing has a predictable delay in RIOT - just the concrete numbers might be to high for some low latency MAC protocols. But again, I'm definitely not arguing against function calls for these tasks, just wondering if function pointers are the smartest solution here. (And I'm not saying that they're not...)

Cheers, Oleg

The reason that I wanted to use function pointers in the NVRAM interface is to avoid having to resort to macros or defining wrapper functions in the board directory for my specific board's NVRAM hardware. As I see it I have three choices for implementing a board specific configuration of NVRAM:

- Macros for the board's functions such as:     board.h:     #define NVRAM_READ(addr, len) nvram_spi_read(MYBOARD_NVRAM_SPI, MYBOARD_NVRAM_CS, addr, len)     #define ....

- board-specific wrapper functions:     board.c:     int nvram_read(addr, len) {         return nvram_spi_read(MYBOARD_NVRAM_SPI, MYBOARD_NVRAM_CS, addr, len)     }     ....

- Function pointers:     board.c:     inside board_init():     nvram_spi_init(&fram, &spi_params, memsize);

I decided on function pointers for the ease of use and the clear interface abstraction.

BR, Joakim Gebart www.eistec.se

Hi Ryan!

In my opinion this is a cleaner abstraction, and leads to clearer parallel composition (or wiring) of modules, rather than the oft used vertical composition.

So, basically you're saying that trading some performance for a cleaner design is a good thing, is that right? I would say that this is very aligned with RIOT's usual design goals and therefore I agree. The question is: how much performance can we give away for this before we hit the limits? Remember, we don't have to care only for rather powerful ARM MCUs, but also for their little 8 and 16 bit brothers and sisters running with only a couple of MHz CPU speed.

I had a bit of a wild rant about this recently, and there is a /very/ draft set of slides if anyone is interested.

I would be interested.

Cheers, Oleg

Hi Joakim!

As I see it I have three choices for implementing a board specific configuration of NVRAM:

- Macros for the board's functions such as:     board.h:     #define NVRAM_READ(addr, len) nvram_spi_read(MYBOARD_NVRAM_SPI, MYBOARD_NVRAM_CS, addr, len)     #define ....

- board-specific wrapper functions:     board.c:     int nvram_read(addr, len) {         return nvram_spi_read(MYBOARD_NVRAM_SPI, MYBOARD_NVRAM_CS, addr, len)     }     ....

- Function pointers:     board.c:     inside board_init():     nvram_spi_init(&fram, &spi_params, memsize);

I decided on function pointers for the ease of use and the clear interface abstraction.

At least for me, it's obvious that the first solution is a no-go. But what made you prefer the chosen function pointer variant over the version where the caller have to specify the low-level devices? If you look at the code of rgbled for example, that's the chosen solution there.

I think we should careful ponder on the pros and cons of both solutions and decide for one for all drivers.

Cheers, Oleg

Hello Oleg,

Hi Kévin!

I began to use function pointers in the 'radio_driver.h' when trying to create a unified model for radio drivers. I guess it went over later to the whole netdev effort.

Yes, I remember that, but why can't this be implemented in a similar way we did for most of the peripheral drivers like SPI? Instead of calling a function pointer, we simply pass a struct describing the low-level device (driver) that should be used.

This is more or less how Contiki actually works -- and what I planned to use in the 'radio_driver.h' mechanism : you use structs... But these structs ultimately contains function pointers. Of course, if this struct is 'const' -- and it *should* be -- it actually translates into a single indirect call in assembler (as I said before).

My idea then was to have a "fast", predictable call chain from the MAC layer to the physical radio transceiver functions: most MAC protocols expect to run with precise delays, and a mechanism relying on message between threads (like 'transceiver') did not fit that need.

I completely agree on the point that we need to have direct function calls for MAC protocols (see also [1502.01968] Old Wine in New Skins? Revisiting the Software Architecture for IP Network Stacks on Constrained IoT Devices).

The model of driver as structs containing function pointers is the one used in Contiki: it seemed quite a natural way to handle the problem to me... In their code, the needed drivers are aliased as 'NETSTACK_xxx' macros, then functions are called by invoking 'NETSTACK_xxx.func()'.

I think it is indeed one of the two natural solutions - the other one would be MACROing everything. I made experiences with both versions while developing on firmware for older sensor board platforms. And both had their drawbacks. Thus, when I started to work on what now is known as RIOT, I wanted to avoid Macros and a function pointer based abstraction layer model as much as possible. Of course, there are cases where either the one or the other will be the best solution, but I think we have to make sure to check if there are no alternative solutions that might work better on the long run.

The const struct of function pointers seems actually cleaner and easier to understand IMHO. Macros in C can quickly turn into a mess if they are used too "liberally". The only macros used in Contiki's netstack model is for choosing the used driver for a given layer. This seems clear enough to me; not to mention that Contiki is known for its efficiency to use constrained devices.

As P. Nikander said, since you are supposed to know your hardware at compile time, these pointers are actually constants that should be resolved at compile (linking) time -- even with GCC. Theoretically, it should be translated by standard indirect calls at the assembler level.

You name it: If everything is known at link time, then there is also always a way around function pointers. As I've written in the previous mail: I'd rather tell the compiler (or linker) how to do the stuff in an efficient way than hoping for the toolchain being smarter than me.

Explicitly mentioning which drivers to use (thanks to simple macros like NETSTACK_xxx), in the board definition for the radio PḦY layer, and in the application Makefile for higher layers, seems quite simple and efficient IMHO...

Could you tell us more about your previous experiments? It seems quite strange to me that such a mechanism (that seems simple to me) could impose speed and debugging penalties.

I cannot disclose any code here, because it was proprietary code for my old company, but the problem was a layered one. We had a highly abstracted driver model, similar to Linux' one with char and block devices, abstracted serial line devices and the like. Thus, an application or any high level code accessing low-level drivers would be de-referenced multiple times. This made it incredible hard to debug (because you had to follow all the pointer de-referencing) and made it almost impossible to hold some strict deadlines for TDMA on our old MSP430F1612.

OK I get the point: your former boss(es) wanted to have a high-level abstraction model, like in Linux. Well, this is like allocating memory at runtime for everything: a good practice on powerful hardware that (more importantly) includes a VMMU -- like our PCs --, but it becomes a chore on less powerful, MMU-less microcontrollers...

As said before, I didn't think to variable function pointers allocated at runtime, but to the "driver as a const struct of function pointers" model.

The other question is how could we implement the network stack? Using a mechanism like 'transceiver', that relies on message-passing between threads, implies that there is no predictable delay for executing network functions (one has to wait for the scheduler to switch to the thread(s) that execute(s) the feature(s) you want to use). At some levels -- like between MAC and PHY -- this can be a huge disadvantage.

I think we can live with the overhead of IPC calls on all layers except the MAC protocol. However, even message-passing has a predictable delay in RIOT - just the concrete numbers might be to high for some low latency MAC protocols. But again, I'm definitely not arguing against function calls for these tasks, just wondering if function pointers are the smartest solution here. (And I'm not saying that they're not...)

Indeed, the most "sensible" link in the netdev chain is probably between the PHY and MAC layers... Well, if we go the high-level way, we could indeed almost revert to message-passing-only API for the all of the netstack; except for the lowest PHY layer, that could use a "const struct" API usable by the MAC/RDC layer. But that actually means reverting all of the netdev effort made by Martine, Hauke and al. during weeks!

The other point I see is that if we use the IPC/message model for the whole netstack, all of its layers will need to be implemented as independent threads, and of course, each of these threads will need their own stack. And here comes another big drawback: this will considerably increase the RAM needed to run the netstack. With a complete stack that goes from PHY up to the app layer, we will certainly eliminate all of the 8 and 16-bit microcontrollers that barely have more than 8 Kb of RAM. In fact, we may even exhaust the MCU that have 32 Kb of RAM... which includes the Cortex-M0+ of the SAMD21/SAMR21!

IMHO, one of the main advantage of the "const struct of function pointers" model, with the faster response time, is the lessening of RAM requirements by diminishing the number of created threads. That's not something negligible...

Cheers, Oleg

Best regards,

Hey,

Hi Kaspar!

>but we don't know which weird >compilers on some esoteric hardware platform might be used for RIOT somewhere >sometime, so I rather rely on optimized C code than on optimized compilers.

this keeps popping up as an excuse not to do some elegant, readable, nice optimizations.

A bit polemic: can't we use Java then and rely on a highly optimized (micro) JVM?

Maybe the question here is if we should concentrate on gcc and clang and keep "weird, esoteric, proprietary compilers that someone might use someday in an not-yet-thought-of use case" as they are - useless.

Was this a vote for a function pointer based HAL or just a Torvalds-like reflex? If the former is the case, then I'm apparently the only one - counting the silent people on this topic as agreeing or not caring - against this approach. In this case I would advice to rethink and remodel the whole driver and HAL design. With the use of function pointers it could be made extremely more (pseudo) object-oriented what make many things much more convenient to implement.

Cheers, Oleg

Hi Oleg,

I vote for "function pointer based HAL" and I think more of them will make RIOT much better :-).

Hi,

A bit polemic: can't we use Java then and rely on a highly optimized (micro) JVM?

:wink:

Maybe the question here is if we should concentrate on gcc and clang and keep "weird, esoteric, proprietary compilers that someone might use someday in an not-yet-thought-of use case" as they are - useless.

Was this a vote for a function pointer based HAL or just a Torvalds-like reflex? If the former is the case, then I'm apparently the only one - counting the silent people on this topic as agreeing or not caring - against this approach. In this case I would advice to rethink and remodel the whole driver and HAL design. With the use of function pointers it could be made extremely more (pseudo) object-oriented what make many things much more convenient to implement.

I remember that over-engineered HAL we invested a lot of work to get out of. :wink:

I don't vote to get back there.

But IMHO we should not take that experience (and yours from that former employee) to dismiss anything involving function pointers or some kind of object orientation, if it looks good in code and doesn't impose runtime costs.

E.g., I'd rather use const function pointers than macros...

Kaspar

Hey Kaspar!

But IMHO we should not take that experience (and yours from that former employee) to dismiss anything involving function pointers or some kind of object orientation, if it looks good in code and doesn't impose runtime costs.

Agreed.

E.g., I'd rather use const function pointers than macros...

I couldn't agree more.

Cheers, Oleg