What's going on here!? ".ramfunc" 's effects on a AVR [Resolved]

Hello!

In a previous topic I disscussed this line of code:

//puts a function into RAM instead of PROGMEM:
<return_type> __attribute__ ((long_call, section (".ramfunc"))) <name>(<args>){
  //...
}

and I was told it does not work on Uno/AtMega328P.

However, I never tested this and just took it as true. Today, however, I just decided to randomly try it out!

It reduced the used program memory by a considerable amount, but had no effect of the global variable size. Using this code:

int freeRam(){
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}

to monitor RAM usage, it does not change between with and without the .ramfunc line. This verifys the compiler’s statement that the RAM usage is unaffected by this line.

What’s going on here? Is the compiler miss- interpreting the size of my sketch? Or does this line actually work with AVR?

Thanks!

AVRs are Harvard architecture CPUs, so they CANNOT run code out of RAM, so that directive can't possibly do anything of any value whatsoever.

Regards,
Ray L.

So what effect am I seeing here? Why does the program get so much smaller?
(also, Dues are harvard, and they have no problem reading from RAM. I know AVRs can't, but "it's harvard" is not the reason)

Fuzzyzilla:
So what effect am I seeing here? Why does the program get so much smaller?
(also, Dues are harvard, and they have no problem reading from RAM. I know AVRs can't, but "it's harvard" is not the reason)

Yes, Harvard IS the reason. A Harvard CPU, by definition, has separate code and data memory spaces. They execute code from one memory, and fetch data from a different memory. What you're seeing is most likely the compiler pretending the directive can do what you want, but the linker doing the only thing it can do - putting that code in FLASH.

Regards,
Ray L.

So assuming (in posted code) that the op is correctly reporting the facts what is happening.

@OP POST YOUR CODE. If you do not ........

Mark

Here is some code demonstrating the effect at hand:
(taken from the “Midi” example)

//WITHOUT ramfunc:
//Sketch uses 1656 bytes (5%) of program storage space. Maximum is 32256 bytes.
//Global variables use 182 bytes (8%) of dynamic memory, leaving 1866 bytes for local variables. Maximum is 2048 bytes.

//WITH ramfunc:
//Sketch uses 1614 bytes (5%) of program storage space. Maximum is 32256 bytes.
//Global variables use 182 bytes (8%) of dynamic memory, leaving 1866 bytes for local variables. Maximum is 2048 bytes.


void setup() {
  //  Set MIDI baud rate:
  Serial.begin(31250);
}
void noteOn(int cmd, int pitch, int velocity);
void loop() {
  // play notes from F#-0 (0x1E) to F#-5 (0x5A):
  for (int note = 0x1E; note < 0x5A; note ++) {
    //Note on channel 1 (0x90), some note value (note), middle velocity (0x45):
    noteOn(0x90, note, 0x45);
    delay(100);
    //Note on channel 1 (0x90), some note value (note), silent velocity (0x00):
    noteOn(0x90, note, 0x00);
    delay(100);
  }
}

//  plays a MIDI note.  Doesn't check to see that
//  cmd is greater than 127, or that data values are  less than 127:
void  __attribute__ ((long_call, section (".ramfunc")))  noteOn(int cmd, int pitch, int velocity) {
  Serial.write(cmd);
  Serial.write(pitch);
  Serial.write(velocity);
}

Remove the attribute from the last function, “noteOn,” to see the effects.

I See no significant change!

//WITHOUT ramfunc:
//Sketch uses 1656 bytes (5%) of program storage space. Maximum is 32256 bytes.
//Global variables use 182 bytes (8%) of dynamic memory, leaving 1866 bytes for local variables. Maximum is 2048 bytes.

//WITH ramfunc:
//Sketch uses 1614 bytes (5%) of program storage space. Maximum is 32256 bytes.
//Global variables use 182 bytes (8%) of dynamic memory, leaving 1866 bytes for local variables. Maximum is 2048 bytes.

Mark

It's not about how significant the difference is, but why there's a difference in the first place.

And I know you aren't going to like this, but in my actual code, the difference is 200 bytes when applying this to the setup function.

My code is too long to post here, and it's completely irrelevant.

Did a bit more testing.

The attribute has actual effects -- The address of a function I tested went from $1B8E to $2FF0 when using the ramfunc attribute.
The function still runs as intended, with no difference in behavior. Calling references to this function also works.

Fuzzyzilla:
why there’s a difference in the first place

The attribute puts the function in a different section. The two sections cannot be merged which forces the linker to move the function to a different location in the image (the end).

Note: I suspect long_call on AVR processors is useless / ignored. … Yup.

Why would the call exist at all? If its NOT Harvard then the function would be in RAM already. It it WAS Harvard It can't put it in RAM at all. There must be some third case or the call would be pointless.

Is there a -almost- Harvard? Maybe some sort of JC?

-jim lee

jimLee:
It it WAS Harvard It can’t put it in RAM at all.

Exactly that. The function is placed in Flash (as expected).

Ok, so the .ramfunc is just shifting it around within flash?

I tested it out, and the long_call has no effect and can be removed from the statement.

But still the question - why the size difference? Can I just put all functions under .ramfunc and magically save space for no reason?

The IDE calculates the program size by adding the sizes of the .data and .text sections from the output of

avr-size -A [i]sketch.ino.elf[/i]

The .ramfunc section is not included in the calculation, but will end up in Flash after the .text section.

without .ramfunc

ramfunc.ino.elf  :
section                     size      addr
.data                         16   8388864
.text                       1640         0
.bss                         166   8388880
.comment                      17         0
.note.gnu.avr.deviceinfo      64         0
.debug_aranges                48         0
.debug_info                 5235         0
.debug_abbrev               2621         0
.debug_line                 1017         0
.debug_frame                 592         0
.debug_str                  1765         0
.debug_loc                  2114         0
.debug_ranges                 80         0
Total                      15375

with .ramfunc

ramfunc.ino.elf  :
section                     size      addr
.data                         16   8388864
.text                       1598         0
.ramfunc                      42      1598
.bss                         166   8388880
.comment                      17         0
.note.gnu.avr.deviceinfo      64         0
.debug_aranges                56         0
.debug_info                 5235         0
.debug_abbrev               2621         0
.debug_line                  971         0
.debug_frame                 592         0
.debug_str                  1765         0
.debug_loc                  2114         0
.debug_ranges                 88         0
Total                      15345

Fuzzyzilla:
Can I just put all functions under .ramfunc ... ?

It's your project. Do what you want with it.

However, putting code in separate sections, just because you can, will make life difficult for the link-time optimizer.

Fuzzyzilla:
Ok, so the .ramfunc is just shifting it around within flash?

I tested it out, and the long_call has no effect and can be removed from the statement.

But still the question - why the size difference? Can I just put all functions under .ramfunc and magically save space for no reason?

You seem to be completely missing the point. There is no magic. You are NOT saving even a single byte of space in either RAM or FLASH by using .ramfunc. The IDE is mis-reporting the FLASH usage with .ramfunc. It is doing this because is assumes, quite reasonably, that using .ramfunc is pointless on an AVR processor, so it ignores the segment that is created by using .ramfunc.

Regards,
Ray L.

oqibidipo:
The IDE calculates the program size by adding the sizes of the .data and .text sections from the output of

avr-size -A [i]sketch.ino.elf[/i]

The .ramfunc section is not included in the calculation, but will end up in Flash after the .text section.

without .ramfunc

ramfunc.ino.elf  :

section                    size      addr
.data                        16  8388864
.text                      1640        0
.bss                        166  8388880
.comment                      17        0
.note.gnu.avr.deviceinfo      64        0
.debug_aranges                48        0
.debug_info                5235        0
.debug_abbrev              2621        0
.debug_line                1017        0
.debug_frame                592        0
.debug_str                  1765        0
.debug_loc                  2114        0
.debug_ranges                80        0
Total                      15375




with .ramfunc


ramfunc.ino.elf  :
section                    size      addr
.data                        16  8388864
.text                      1598        0
.ramfunc                      42      1598
.bss                        166  8388880
.comment                      17        0
.note.gnu.avr.deviceinfo      64        0
.debug_aranges                56        0
.debug_info                5235        0
.debug_abbrev              2621        0
.debug_line                  971        0
.debug_frame                592        0
.debug_str                  1765        0
.debug_loc                  2114        0
.debug_ranges                88        0
Total                      15345

Note carefully that the .text + .ramfunc size in the second output is exactly equal to the .text size in the first. This supports RayLivingston's statement that you are not actually saving a single byte, it's just that the IDE is not including this section when it calculates the size of the sketch, so it's undercounting the size.

Also:

warning: 'long_call' attribute directive ignored [-Wattributes]

long_call is indeed ignored.

Interestingly, I can't see what you saw with version 1.8.2. Here's my test sketch.

/*
  Blink
  Turns on an LED on for one second, then off for one second, repeatedly.

  Most Arduinos have an on-board LED you can control. On the UNO, MEGA and ZERO 
  it is attached to digital pin 13, on MKR1000 on pin 6. LED_BUILTIN is set to
  the correct LED pin independent of which board is used.
  If you want to know what pin the on-board LED is connected to on your Arduino model, check
  the Technical Specs of your board  at https://www.arduino.cc/en/Main/Products
  
  This example code is in the public domain.

  modified 8 May 2014
  by Scott Fitzgerald
  
  modified 2 Sep 2016
  by Arturo Guadalupi
  
  modified 8 Sep 2016
  by Colby Newman
*/

#define RAM_FUNC

#ifdef RAM_FUNC
  #define RAM_ATT __attribute__ ((section (".ramfunc")))
#else
  #define RAM_ATT
#endif

// the setup function runs once when you press reset or power the board
void RAM_ATT setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

// the loop function runs over and over again forever
void RAM_ATT loop() {
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);                       // wait for a second
}

Size is 928 bytes whether I have the attribute defined or not.

I'm a bit surprised that the separate section (.ramfunc) doesn't disappear during link for an AVR. All the other code-like sections are merged, and by the time link is done the tools should have figured out there isn't going to be any actual ram-based code...

westfw:
I'm a bit surprised that the separate section (.ramfunc) doesn't disappear during link for an AVR.

That surprised me as well.

After some time with Google it appears the linker does the best it can to match unknown sections to known ones (e.g. code goes with code) and puts unknown sections in the order they appear to the linker after known ones. ("Known sections" are ones defined in the linker script.)

All the other code-like sections are merged...

I believe sections are only merged if that is allowed by linker script. If they cannot be merged they are placed as neighbors. For example, ".init0" cannot be merged with other sections, it has to be placed after ".dtors", and it has to be placed before ".init1".

With no rule, ".ramfunc" is put with other code because that's what is in the section, it is placed after all other sections, and it cannot be merged with other sections.

In any case, using ".ramfunc" is a bad idea when building for an AVR processor. In the best case it interferes with optimization and in the worst case it is undefined (I cannot find documentation to back may claims; just inferences.)

What's going on here!? ".ramfunc" 's effects on a AVR [unsolved]

In what way is this unsolved?