When compile time functions shines

Go To Last Post
5 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Wait, I'm not talking about metafunctions, for sure compile time functions on types are something essential to metaprogramming, which can indeed deliver powerful abstractions to the user when some tradeoffs are bought. I'm referring to functions on objects at compile time, aka, constexpr or consteval functions. I came across to a interesting problem when developing a driver to an OLED display dot matrix that has GDDRAM(Graphics Display RAM) to represent each dot(pixel) as one bit, yes, bitmaps! But there is a catch, the bitmap is represented in the RAM rotated 90 degrees clockwise. So, it's very useful and expressive declare something like this in the code:

constexpr static const uint8_t bitmap_h[] = {
    0b10000000, 
    0b10000000,
    0b10111000,
    0b11000100,
    0b10000100,
    0b10000100,
    0b10000100,
    0b10000100,
};

But actually we need something like this to be sent to the device:

static const uint8_t letter_h[] [[gnu::__progmem__]] = {
    0b11111111,
    0b00001000,
    0b00000100,
    0b00000100,
    0b11111000,
    0b00000000,
    0b00000000,
};

Note that we are using __progmem__ because it's almost imperative to put the charset in the memory flash when using tiny devices. It would be perfect if the last array assigned to letter_h was the result of a function called at compile time that receives bitmap_h. We can easily do that using C++17:

constexpr uint8_t rotate(uint8_t col, uint8_t b, uint8_t pos) {
    if(pos > col) b >>= pos - col;
    else if (pos < col) b <<= col - pos;
    return b &= 1<<(7 - pos);
}

 

struct bitmap_90_clockwise { uint8_t data[8]{}; };

 

/** Transform a bitmap to a sequence of bytes to be sent to the
    GDDRAM. Basically the bitmap is rotated 90 degress clockwise.
 */
constexpr bitmap_90_clockwise toGDDRAM(const uint8_t(&bitmap)[8]) {
    bitmap_90_clockwise ret;
    for(uint8_t col{0}; col < 8; ++col)
        for(uint8_t row{0}; row < 8; ++row) 
            ret.data[col] |= rotate(col, bitmap[row], 7 - row);
    return ret;
}

 

Now, we can rewrite the assignment to letter_h:

 

static const bitmap_90_clockwise letter_h [[gnu::__progmem__]] = toGDDRAM(bitmap_h);
 

This isn't only an expressive way to describe each letter(symbol) using the normal(human) orientation but it's also useful because it allows the programmer to make adjustments easily in the bitmap.

 

github.com/ricardocosme

avrIO: Operation of I/O port pins and I/O registers.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

On the other hand, a 'normal' orientation bitmap modified externally i.e. on a different computer, not in the compiler or on the chip itself, has the advantage that the resulting bit pattern can easily be generated for any language - C, C++, assembly, whatever that you might choose to program the device in. A similar routine converts a splash screen bitmap image, with the output arranged so that a straight copy along a row transfers a complete slice of image, eight pixels deep.

 

But I will agree, not as obvious in the code itself:

const uint8_t fonts[960] = {
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 	// ' '
	0x00, 0x00, 0x00, 0x00, 0x0B, 0xF8, 0x00, 0x00, 0x00, 0x00, 	// '!'
	0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 	// '"'
	0x02, 0x20, 0x0F, 0xF8, 0x02, 0x20, 0x0F, 0xF8, 0x02, 0x20, 	// '#'

Successive pairs of bytes represent the upper and lower half of a sixteen bit vertical slice (I use a 16 x 6 character cell, with the bottom 4 cells empty (but included in the bitmap) and the right hand column blank but not stored.

 

Have I misunderstood your post, or is the rotation function executed only at compile time, in spite of looking superficially like normal executable code? I'm not sure I like the idea of that, if so.

 

Neil

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

>On the other hand, a 'normal' orientation bitmap modified externally i.e. on a different computer, not in the compiler or on the chip itself, has the advantage that the resulting bit pattern can easily be generated for any language - C, C++, assembly, whatever that you might choose to program the device in. A similar routine converts a splash screen bitmap image, with the output arranged so that a straight copy along a row transfers a complete slice of image, eight pixels deep.

It's another approach with its own advantages and disadvantages. To the usage that I have mentioned in the post I prefer to keep the bitmap in the code because is simpler to my taste.

 

>But I will agree, not as obvious in the code itself:

const uint8_t fonts[960] = {
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 	// ' '
	0x00, 0x00, 0x00, 0x00, 0x0B, 0xF8, 0x00, 0x00, 0x00, 0x00, 	// '!'
	0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 	// '"'
	0x02, 0x20, 0x0F, 0xF8, 0x02, 0x20, 0x0F, 0xF8, 0x02, 0x20, 	// '#'

>Successive pairs of bytes represent the upper and lower half of a sixteen bit vertical slice (I use a 16 x 6 character cell, with the bottom 4 cells empty (but included in the bitmap) and the right hand column blank but not stored.

Yeah, it isn't obvious.

 

>Have I misunderstood your post, or is the rotation function executed only at compile time, in spite of looking superficially like normal executable code? I'm not sure I like the idea of that, if so.

The functions toGDDRAM and rotate are executed only at compile time.

 

github.com/ricardocosme

avrIO: Operation of I/O port pins and I/O registers.

Last Edited: Mon. Apr 5, 2021 - 06:32 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

rcosme wrote:
I'm referring to functions on objects at compile time, aka, constexpr or consteval functions.

 

But your example does not really demonstrate any "compile time functions". Declaring a function as `constexpr` does not necessarily turn it into a compile time function. As long as your final recipient array is not declared `constexpr` (and it is not), nothing in your code is required to be "compile time".

 

P.S. Apparently,  [[gnu::__progmem__]] is supposed to enforce compile-time evaluation. Does it really work that way?

Dessine-moi un mouton

Last Edited: Tue. Apr 6, 2021 - 05:14 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

>But your example does not really demonstrate any "compile time functions". Declaring a function as `constexpr` does not necessarily turn it into a compile time function. As long as your final recipient array is not declared `constexpr` (and it is not), >nothing in your code is required to be "compile time".

 

>P.S. Apparently,  [[gnu::__progmem__]] is supposed to enforce compile-time evaluation. Does it really work that way?

Yes.

 

In this case the constexpr functions are executed at compile time. We can use consteval at C++20 to be more clear and maybe safe about that.

github.com/ricardocosme

avrIO: Operation of I/O port pins and I/O registers.