BigBobby:
OK, one last test (and apparently I'm the only one that still cares).
I can reproduce your results. It is generating the wanted code, and on the face of it, a bit more.
BigBobby:
OK, one last test (and apparently I'm the only one that still cares).
I can reproduce your results. It is generating the wanted code, and on the face of it, a bit more.
Do you have an idea why the code would stop after "a = " is printed?
The assembly for those print statements look normal, but I doubt that's where the problem is anyway. It probably loads the value for "a" into the serial buffer just fine but gets hosed before it prints to the serial port.
Very confusing.
I tried flushing serial and then going into an infinite loop, but the variable still isn't printed.
ie.
Serial.println (a);
Serial.flush ();
while (true) ;
In your most recent sketch, if you remove the one word PROGMEM it runs OK. The generated code is (surprisingly) almost the same length.
I think somehow you have confused the compiler into thinking it has to generate things at runtime, despite the PROGMEM directive.
Why it crashes at that point is a mystery.
BigBobby:
Do you have an idea why the code would stop after "a = " is printed?
I do. Here is what is happening...
Your example is valid C++. Obviously. So the compiler faithfully generates code.
Included in that generated code is a hint to the linker that NOTE_LENGTH should be placed in a particular section. The linker honours that request. It sets aside space for NOTE_LENGTH in the requested section then puts the NOTE_LENGTH address in the correct code slots.
NOTE_LENGTH can only be initialized at run-time. So, the compiler generates stubs that calculate a value for each element in NOTE_LENGTH then store that value in the correct location.
The stubs are specifically coded to store their calculated values in SRAM. They have no concept of Flash versus SRAM. The problem is that the addresses for NOTE_LENGTH are supposed to be in Flash. Essentially, the stubs are poking around in SRAM leaving it horribly corrupt. Which is why the sketch goes haywire.
I have it down to a minimal example:
float foo(float x)
{
return x;
}
#define cl(n) (84 + 12*(foo(n/100.0) / log(2)))
const unsigned char NOTE_LENGTH[] PROGMEM = {cl(0), cl(1), cl(2), cl(3), cl(4), cl(5), cl(6), cl(7), cl(8), cl(9)};
void setup()
{
Serial.begin(115200); // Initialize the serial port
Serial.println("Starting");
}
void loop()
{
Serial.println("Hello");
Serial.println("world");
delay (100);
}
This prints gibberish. In fact it's so bad I can't copy and paste it. Retyping, it looks like this:
He?Starting
He?Starting
He?Starting
He?St?Start ?Start Start Start
So it looks like the processor is resetting and doing something weird.
That makes sense.
BigBobby:
OK, one last test (and apparently I'm the only one that still cares). I switched to using the notlog function which shouldn't be something that it can calculate at compile time. Also, I'm using Arduino 1.6.8.
I think you are creating headaches for yourself by working on BOTH understanding which expressions gets evaluated at compile time, and which get evaluated at run-time, AND then trying to shove things into PROGMEM. They are two separate, totally unrelated issues. First, understand expression evaluation, without PROGMEM. Once you have that down, THEN try to figure out how to put the data into PROGMEM. I would not be at all surprised if there is (seemingly valid) syntax that gets confused and screws up when PROGMEM is added to the equation. Obviously, you can't put expressions that can only be evaluated at run time into PROGMEM....
Regards,
Ray L.
shawnlg:
So I wonder which functions will gcc run at compile time. Is there a way to know?
Google. Reply #7 has the key-phrase. You will have to add two items to your search.
Or does it "realize" that everything is constants and just run it?
Wikipedia has a good article on the subject. Reply #7 has the key-phrase.
I wonder, did you read reply #7?
No functions are run at compile tine. Marcos are expanded and constants folded but no functions are run.
The compiler does NOT use its local/own copy of a function nor does it interpret the code.
The idea that some functions are run at compile time is totally wrong!
Mark
holmes4:
The idea that some functions are run at compile time is totally wrong!
void setup()
{
Serial.begin(115200);
Serial.println( log( 2 ) );
}
void loop() { }
000000be <setup>:
public:
inline HardwareSerial(
volatile uint8_t *ubrrh, volatile uint8_t *ubrrl,
volatile uint8_t *ucsra, volatile uint8_t *ucsrb,
volatile uint8_t *ucsrc, volatile uint8_t *udr);
void begin(unsigned long baud) { begin(baud, SERIAL_8N1); }
be: 26 e0 ldi r18, 0x06 ; 6
c0: 40 e0 ldi r20, 0x00 ; 0
c2: 52 ec ldi r21, 0xC2 ; 194
c4: 61 e0 ldi r22, 0x01 ; 1
c6: 70 e0 ldi r23, 0x00 ; 0
c8: 82 e2 ldi r24, 0x22 ; 34
ca: 91 e0 ldi r25, 0x01 ; 1
cc: 0e 94 56 01 call 0x2ac ; 0x2ac <_ZN14HardwareSerial5beginEmh>
void setup()
{
Serial.begin(115200); //! Initialize the serial port to the PC
Serial.println( log( 2 ) );
d0: 22 e0 ldi r18, 0x02 ; 2
d2: 30 e0 ldi r19, 0x00 ; 0
d4: 48 e1 ldi r20, 0x18 ; 24
d6: 52 e7 ldi r21, 0x72 ; 114
d8: 61 e3 ldi r22, 0x31 ; 49
da: 7f e3 ldi r23, 0x3F ; 63
dc: 82 e2 ldi r24, 0x22 ; 34
de: 91 e0 ldi r25, 0x01 ; 1
e0: 0c 94 2a 04 jmp 0x854 ; 0x854 <_ZN5Print7printlnEdi>
000000e4 <loop>:
}
void loop() { }
e4: 08 95 ret
No call to log.
holmes4:
No functions are run at compile tine. Marcos are expanded and constants folded but no functions are run.The compiler does NOT use its local/own copy of a function nor does it interpret the code.
The idea that some functions are run at compile time is totally wrong!
Mark
User-defined "functions" will NEVER run at compile time. There is simply no mechanism built into the compiler to enable that. But the compiler CAN evaluate expressions, even those using common math functions (trig functions, log, etc.), to do constant folding. Whether or not THIS compiler does that is another question. But some compilers absolutely do.
Regards,
Ray L.
RayLivingston:
User-defined "functions" will NEVER run at compile time.
I really wish you two would test your theories before posting. Or ask.
static int ugh( int const a, int const b ) __attribute__((const));
static int ugh( int const a, int const b )
{
return( a * b );
}
void setup()
{
Serial.begin(115200);
Serial.println( log( 2 ) );
Serial.println( ugh( 2, 3 ) );
}
void loop() { }
000000be <setup>:
public:
inline HardwareSerial(
volatile uint8_t *ubrrh, volatile uint8_t *ubrrl,
volatile uint8_t *ucsra, volatile uint8_t *ucsrb,
volatile uint8_t *ucsrc, volatile uint8_t *udr);
void begin(unsigned long baud) { begin(baud, SERIAL_8N1); }
be: 26 e0 ldi r18, 0x06 ; 6
c0: 40 e0 ldi r20, 0x00 ; 0
c2: 52 ec ldi r21, 0xC2 ; 194
c4: 61 e0 ldi r22, 0x01 ; 1
c6: 70 e0 ldi r23, 0x00 ; 0
c8: 82 e2 ldi r24, 0x22 ; 34
ca: 91 e0 ldi r25, 0x01 ; 1
cc: 0e 94 5e 01 call 0x2bc ; 0x2bc <_ZN14HardwareSerial5beginEmh>
}
void setup()
{
Serial.begin(115200);
Serial.println( log( 2 ) );
d0: 22 e0 ldi r18, 0x02 ; 2
d2: 30 e0 ldi r19, 0x00 ; 0
d4: 48 e1 ldi r20, 0x18 ; 24
d6: 52 e7 ldi r21, 0x72 ; 114
d8: 61 e3 ldi r22, 0x31 ; 49
da: 7f e3 ldi r23, 0x3F ; 63
dc: 82 e2 ldi r24, 0x22 ; 34
de: 91 e0 ldi r25, 0x01 ; 1
e0: 0e 94 4a 04 call 0x894 ; 0x894 <_ZN5Print7printlnEdi>
Serial.println( ugh( 2, 3 ) );
e4: 4a e0 ldi r20, 0x0A ; 10
e6: 50 e0 ldi r21, 0x00 ; 0
e8: 66 e0 ldi r22, 0x06 ; 6
ea: 70 e0 ldi r23, 0x00 ; 0
ec: 82 e2 ldi r24, 0x22 ; 34
ee: 91 e0 ldi r25, 0x01 ; 1
f0: 0c 94 47 03 jmp 0x68e ; 0x68e <_ZN5Print7printlnEii>
No call to ugh.
Before protesting about this example, bear in mind the GCC compiler is often capable of constant folding with non-trivial functions including ones that allow non-const parameters.
holmes4:
The idea that some functions are run at compile time is totally wrong!
I think (hope) you mean "user-written functions". It would be easy enough for compiler-writers to fold calculations like SQRT and LOG into a constant. Unless you are expecting to implement a SQRT function at runtime that, well, does something other than return a square root.
Thanks for the explanation and sorry it took a while to respond (tied up with work).
Your explanation sounds good, but wouldn't this stuff happen before setup() even ran? It seems that the compiler would do this and corrupt the SRAM before any other code had used it. At that point I'd expect it to either 1) have no affect at all or 2) stop us from even getting to Serial.print at all?
That said, the PROGMEM addresses used in my first example using log() instead of notlog() started at 0xC2. In the SRAM space that would be the Ext I/O registers. Maybe there is potential to leave a ticking time bomb there.
I would be very surprised if there were any significant limitations on the functions that could be used in a compile-time expressions
The problem comes from the thought that C is normally pretty strict about separating the features of the language itself from the capabilities of any libraries that might be available. Expressions are defined by the language, but functions (including things like log()) are provided by libraries, and one mode of thought says that the compiler ought not be assuming that it can evaluate log(2), because log() could be provided by an external library, and perhaps doesn't do logarithm-base-e that the compiler expects. I think newer versions of the language specification are more careful about defining function names that ARE considered part of the compiler (and woe to him who reuses one of those names!), and the compiler will even use external functions that had better behave as per the standard (memcpy())
Nevertheless, I've spent enough time using a non-standard printf() that "new" compiler features that (for example) make sure that printf() arguments match the format string make me a bit uncomfortable about this sort of behavior...
Never say never. Or always. I like "maybe":
“The ability of a compiler, that would normally compile a function to machine code and execute it at run time, to execute the function at compile time is possible if the arguments to the function are known at compile time, and the function does not make any reference to or attempt to modify any global state (is a pure function).”
Generalized constant expressions (constexpr) has been a feature of GCC since version 4.6:
GCC builtin functions:
My cursory look at your code determined that Coding Badly nailed why it doesn’t work.
I hope this helps.
BigBobby:
Thanks for the explanation...
You are welcome.
Your explanation sounds good, but wouldn't this stuff happen before setup() even ran?
Exactly. Even before main is called.
It seems that the compiler would do this...
The entity that handles such things is usually named "crt initialization". The "crt" is "C run-time". Often, the object file is named "crt0". It would be a part of Libc.
...and corrupt the SRAM before any other code had used it.
Exactly.
At that point I'd expect...
That's the fun of corrupt memory. You can toss your exceptions out the window. I have witnessed truly amazing behaviour as a result of corrupt memory. The behaviour in this case is not surprising to me.
Maybe there is potential to leave a ticking time bomb there.
Yup.
Hmm...maybe I'll use the AVR Dragon when I get back home to single step through c_init() to figure out exactly what is getting stomped on. Even though corrupting a block of the I/O space before main() can certianly be a ticking time bomb, it still seems odd how long after main() starts the micro loses its mind. Also, adding code before the Serial.print()s didn't affect where the mind-losing occurred. It seems like it is related to the serial port, which I'd have expected the Arduino init() to have un-stomped after c_init() stomped it.
And yeah, corrupted memory can really result in just about anything happening. I discovered an example recently in my IAR Workbench project for my ARM Cortex M3. First off, instead of allocating all unused memory to the stack, it was only allocating 2kB. Next, instead of allocating the stack at the end of memory so that I'd get an exception upon stack overflow, it allocated it at the bottom so that stack overflow resulted in random variables being corrupted! And I paid $5k for that compiler...
JimEli:
Never say never. Or always. I like "maybe":“The ability of a compiler, that would normally compile a function to machine code and execute it at run time, to execute the function at compile time is possible if the arguments to the function are known at compile time, and the function does not make any reference to or attempt to modify any global state (is a pure function).”
Generalized constant expressions (constexpr) has been a feature of GCC since version 4.6:
GCC builtin functions:
My cursory look at your code determined that Coding Badly nailed why it doesn’t work.
I hope this helps.
In the "ugh() example", the function does nothing but multiply the two passed arguments, and return the result. I would expect the optimizer to be smart enough to convert that to an inline function, as that would likely create faster, smaller code than inserting the function call. In the case where both arguments are constants (as in the example) this turns the whole "function call" into a constant expression, which, again, the optimizer should be smart enough to realize can be evaluated at compile time. So, the result of that trivial example is not at all surprising. to me
For more complex expressions, like those containing calls to functions like log, sin, etc, there will certainly be variation from one compiler to another in how they process the expressions. It appears, from examples in this thread, that avr-gcc can, and will, fully evaluate SOME such expressions at compile time, when the expressions otherwise reduce to constant expressions. It would be interesting to see if some more complex expressions, like those using trig functions, etc, would also be evaluated at compile time. I'd guess some/many will.
However, I would guess, and it is just a guess, that more complex functions, that would not be logical choices for in-lining, due to their size and complexity, would NOT be compile-time evaluated, and would generate run-time function calls instead, despite their reducing to, technically, "constant functions" in some invocations.
It would be interesting to test, but I don't have the time to mess with it. Hopefully someone else will.
Regards,
Ray L.
When us and cc are compile-time constants, this reduces to a simple constant...