Compile-time constants

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

Could somebody offer some insight as to why this expression does not evaluate to a compile-time constant ?

& (* (volatile uint8_t *) (N + M));

N and M are constants.

I'm using the latest AS6.

Sid

Life... is a state of mind

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

No, I can't see why it is not a constant.

OTOH, why don't you just say:

(volatile uint8_t *) (N + M)

which would be the usual way to refer to a memory-mapped hardware peripheral. And I bet would compile efficiently.

OTOH, why do you care? Will it make a serious difference to your program's efficiency?

David.

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

ChaunceyGardiner wrote:
Could somebody offer some insight as to why this expression does not evaluate to a compile-time constant ?
& (* (volatile uint8_t *) (N + M));

N and M are constants.

Whatever it is, it should be the same as
(volatile uint8_t *) (N + M);

It will not be a constant expression.
The definition of a constant expression does not include pointers.
That said, it should be something that the compiler can determine.
In any case, as a statement it is a no-op.

Moderation in all things. -- ancient proverb

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

david.prentice wrote:
No, I can't see why it is not a constant.
Me neither. That is why I am asking.

david.prentice wrote:
OTOH, why don't you just say:

(volatile uint8_t *) (N + M)


Somebody else, i.e. the Atmel supplied header files, alredy said
* (volatile uint8_t *) (N + M)

david.prentice wrote:
OTOH, why do you care? Will it make a serious difference to your program's efficiency?

No, it wouldn't make any difference in efficiency whatsoever and that is the beauty of it. If it worked like it should, I could use it to make a big improvement in source code readability (and maintenance).

skeeve wrote:

(volatile uint8_t *) (N + M);

will not be a constant expression.
The definition of a constant expression does not include pointers.


How come ?

Are you saying that x is not assigned a constant value known at compile time in this case:

int x = (int) (volatile uint8_t *) (N + M);

skeeve wrote:
In any case, as a statement it is a no-op.

Obviously. It still has to be evaluated at compile time, though, and that is what I was asking about.

Sid

Life... is a state of mind

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

This code compiles in Visual C++ but not in GCC:

template
class C
{
    public:
        uint8_t get() { return *c; }
};

#define SOME_ADDRESS ((volatile uint8_t *) 123)

void f()
{
    C c;
    c.get();
}

The same code modified to use references instead of pointers fails on both platforms.

My conclusion is that you can forget about it altogether in GCC, unless Atmel changes the header files to provide constants that gives us addresses as ints as well as macros to change those ints to pointers at request. That's not going to happen.

Bummer.

Sid

Life... is a state of mind

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

Further experiments with hardcoded addresses indicates that it is possible to do what I want to do, at the expense of portability, but GCC sucks big time when it comes to template code generation for anything not instantiated and kept in the local scope.

That is, declaring and using a template instance inside main() generates a free ride in terms of code and data size but moving the declaration to the global scope adds quite a bit to the code size even when the variable is declared static and still only used in main(). It makes no sense whatsoever.

Sid

Life... is a state of mind

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

ChaunceyGardiner wrote:
skeeve wrote:

(volatile uint8_t *) (N + M);

will not be a constant expression.
The definition of a constant expression does not include pointers.


How come ?
That is just part of the grammar of C.
Quote:
Are you saying that x is not assigned a constant value known at compile time in this case:
int x = (int) (volatile uint8_t *) (N + M);

No. Depending on the compiler, it might or might not be.
Quote:

skeeve wrote:
In any case, as a statement it is a no-op.

Obviously. It still has to be evaluated at compile time, though, and that is what I was asking about.
No.
The original example does not need to be evaluated at all.
It is a no-op.

Moderation in all things. -- ancient proverb

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

skeeve wrote:
ChaunceyGardiner wrote:
skeeve wrote:
In any case, as a statement it is a no-op.

Obviously. It still has to be evaluated at compile time, though, and that is what I was asking about.
No.
The original example does not need to be evaluated at all.
It is a no-op.

Huh ? How is the compiler going to know that without evaluating it ?

Sid

Life... is a state of mind

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

What optimisation level are you using, and what is the exact header file constant you are using?

- Dean :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

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

I'm using -Os and the header file constants I wanted to use are PINx, PORTx and DDRx.

If it had turned out to work in an acceptable manner, I would have wanted to use other constants, too. But as it happens the GCC generated code (for the equivalent int representation) throws in too much unnecessary overhead, so I wouldn't pursue this even if Atmel added the necessary #defines.

Sid

Life... is a state of mind

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

ChaunceyGardiner wrote:
Could somebody offer some insight as to why this expression does not evaluate to a compile-time constant ?
& (* (volatile uint8_t *) (N + M));

N and M are constants.

It's not enough that and N and M are constants. They must be compile time constants.

If N and M are compiler time constants, then GCC evaluates N + M at compiler time, even if optimization is turned off.

In absence of a valid test case, I allowed to add the lines you cut away for obfuscation and confusion, in order to yield a test case that can be compiled:

typedef unsigned char uint8_t;

#define M 0xbabe
#define N 0xface

volatile uint8_t* f (void)
{
    return & (* (volatile uint8_t *) (N + M));
}

Compiled with

    avr-gcc -S foo.c
we get the following output from
    gcc version 4.6.2 (AVR_8_bit_GNU_Toolchain_3.4.0_663)
f:
	push r28
	push r29
	in r28,__SP_L__
	in r29,__SP_H__
	ldi r24,lo8(-19060)
	ldi r25,hi8(-19060)
	pop r29
	pop r28
	ret

avrfreaks does not support Opera. Profile inactive.

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

OT: And, why is the prologue/epilogue, Johann?

Jan

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

Because it is compiled without optimization, and then a frame pointer is set up.

avrfreaks does not support Opera. Profile inactive.

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

ChaunceyGardiner wrote:
I'm using -Os and the header file constants I wanted to use are PINx, PORTx and DDRx.
PINx, PORTx and DDRx are not constants.
If you used one of those for N or M, therein lies a problem.

Moderation in all things. -- ancient proverb

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

SprinterSB wrote:
Because it is compiled without optimization, and then a frame pointer is set up.
Even if there's no frame? It sounds to me more like pesimisation... :-)

Thanks for the info.

JW

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

wek wrote:
SprinterSB wrote:
Because it is compiled without optimization, and then a frame pointer is set up.
Even if there's no frame? It sounds to me more like pesimisation...
There *is* a frame. It's size is 0. There is a frame because a frame pointer is set up for it.

Options like -fomit-frame-pointer take effect with higher optimization levels and — depending on option and optimization level — are turned on then. -fomit-frame-pointer is an optimization, and it is an optimization that derails some types of debugging like call frame debugging.

-O0 says

    "Optimize for maximum debugging information and smallest resource consumption on the machine that hosts the compiler."
For both requirements, -fno-omit-frame-pointer is the right choice.

Also notice that code generation must not depend on the debug format or level selected with -g, i.e. omitting -g must not produce smaller code.

avrfreaks does not support Opera. Profile inactive.

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

SprinterSB wrote:
ChaunceyGardiner wrote:
Could somebody offer some insight as to why this expression does not evaluate to a compile-time constant ?
& (* (volatile uint8_t *) (N + M));

N and M are constants.

It's not enough that and N and M are constants. They must be compile time constants.

They are. I thought that was obvious from my previous posts.

SprinterSB wrote:
If N and M are compiler time constants, then GCC evaluates N + M at compiler time, even if optimization is turned off.

I do know that. That is not what my question was about. I was asking why
& (* (volatile uint8_t *) (N + M));

does not evaluate to a compile-time constant. That question was meant to imply that I want to use this compile-time constant during compilation.

SprinterSB wrote:
the lines you cut away for obfuscation and confusion

Where on Earth did that come from ? I included the information I thought was necessary to get an intelligent discussion about a generic problem. I also stated (in my second post) that the constants I'm using came from an Atmel header file.

I understand that I did not include enough information for you to understand this. Going from "I need more information" to "you're deliberately trying to confuse me" is still quite a leap. If I wanted to obfuscate and confuse - what would be the point of asking the question in the first place ?

SprinterSB wrote:
In absence of a valid test case, I allowed to add the lines you cut away for obfuscation and confusion, in order to yield a test case that can be compiled:

typedef unsigned char uint8_t;

#define M 0xbabe
#define N 0xface

volatile uint8_t* f (void)
{
    return & (* (volatile uint8_t *) (N + M));
}


Unfortunately, that does not illustrate the problem. I posted one version of it earlier, but I guess that wasn't clear enough.

Try compiling this:

template
class C
{
};

#define SOME_ADDRESS ((volatile uint8_t *) 123)

void f()
{
    C<123> c1;
    C<(int) SOME_ADDRESS> c2;
}

It stands to reason that if the declaration of c1 is okay, so is the declaration of c2. Unfortunately, the compiler disagrees.

Sid

Life... is a state of mind

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

skeeve wrote:
ChaunceyGardiner wrote:
I'm using -Os and the header file constants I wanted to use are PINx, PORTx and DDRx.
PINx, PORTx and DDRx are not constants.

They are variables located at a constant address available at compile-time. As I am using their addresses, I am using them as constants.

skeeve wrote:
If you used one of those for N or M, therein lies a problem.

If you look up the definition of PINx, PORTx or DDRx you will see that N and M are a part of that definition.

Sid

Life... is a state of mind

Last Edited: Sun. Sep 9, 2012 - 09:55 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ChaunceyGardiner wrote:
Try compiling this:

template
class C
{
};

#define SOME_ADDRESS ((volatile uint8_t *) 123)

void f()
{
    C<123> c1;
    C<(int) SOME_ADDRESS> c2;
}

It stands to reason that if the declaration of c1 is okay, so is the declaration of c2. Unfortunately, the compiler disagrees.

The compiler is correct.
It is not enough that the template argument be an int known at compile time.
The argument must be a constant expression.
Constant expressions do not have pointer expressions as sub-expressions.
c2's declaration is syntactically wrong,
though allowing it would be a sensible extension.

Moderation in all things. -- ancient proverb

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

skeeve wrote:
The compiler is correct.
It is not enough that the template argument be an int known at compile time.
The argument must be a constant expression.
Constant expressions do not have pointer expressions as sub-expressions.
c2's declaration is syntactically wrong

I know that the compiler deems this wrong, that is why I started this topic by asking why it is wrong.

You are not offering any insight as to why it is wrong, you are just saying it's wrong because it is wrong.

skeeve wrote:
allowing it would be a sensible extension

I am happy to see that we agree about that.

Sid

Life... is a state of mind

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

ChaunceyGardiner wrote:
Try compiling this:
template
class C
{
};

#define SOME_ADDRESS ((volatile uint8_t *) 123)

void f()
{
    C<123> c1;
    C<(int) SOME_ADDRESS> c2;
}


This cannot work. You have to define or typedef uint8_t.

Then you have to pick the right C++ version to support this. For supported language standards and command options to enable them, see the GCC user manual. C++0x or C++11 work with avr-g++ and also GNU C++0x and GNU C++11 do.

avrfreaks does not support Opera. Profile inactive.

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

SprinterSB wrote:
This cannot work. You have to define or typedef uint8_t.

That response says it all.

I give up.

Sid

Life... is a state of mind

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

ChaunceyGardiner wrote:
skeeve wrote:
The compiler is correct.
It is not enough that the template argument be an int known at compile time.
The argument must be a constant expression.
Constant expressions do not have pointer expressions as sub-expressions.
c2's declaration is syntactically wrong

I know that the compiler deems this wrong, that is why I started this topic by asking why it is wrong.

You are not offering any insight as to why it is wrong, you are just saying it's wrong because it is wrong.

I'm saying it's part of the definition of the language,
just like three strikes and you're out is part of the definition of baseball.
If you really need a reason, think of it this way:
For most pointers (int)ptr is not an expression the compiler could evaluate at compile time.
To syntactically allow (int)ptr to be a constant expression in cases where the compiler could evaluate it would require enlarging an already large grammar.
Quote:
skeeve wrote:
allowing it would be a sensible extension

I am happy to see that we agree about that.
You might try something like this:
#define compile_time_assert(exp) do { if(!(exp)) none_such(); } while(0)

#define SOME_ADDRESS_INT 123

compile_time_assert(SOME_ADDRESS==(volatile uint8_t*)SOME_ADDRRESS_INT);

Use SOME_ADDRESS when you want a pointer and SOME_ADDRESS_INT when you want an integer.
If SOME_ADDRESS_INT is correct and the comparison can be evaluated at compile time,
none_such should not be referenced.
In case of an error, a link failure should occur.

Automatic checking against Atmel's header file might
not be as good as deriving from Atmel's header file,
but it's better than nothing.

Moderation in all things. -- ancient proverb

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

skeeve wrote:
You might try something like this:
#define compile_time_assert(exp) do { if(!(exp)) none_such(); } while(0)

#define SOME_ADDRESS_INT 123

compile_time_assert(SOME_ADDRESS==(volatile uint8_t*)SOME_ADDRRESS_INT);

Use SOME_ADDRESS when you want a pointer and SOME_ADDRESS_INT when you want an integer.
If SOME_ADDRESS_INT is correct and the comparison can be evaluated at compile time,
none_such should not be referenced.
In case of an error, a link failure should occur.

Automatic checking against Atmel's header file might
not be as good as deriving from Atmel's header file,
but it's better than nothing.


Yes, that is an excellent safeguard for the hardcoded int workaround. I would definitely use that if the quality of the generated template code was good enough to actually use it.

It would have to work with SOME_ADDRESS having the form
& ( * (volatile uint8_t *) N), though - because of the format used in the Atmel headers.

Sid

Life... is a state of mind

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

ChaunceyGardiner wrote:
GCC sucks big time when it comes to template code generation for anything not instantiated and kept in the local scope.

That is, declaring and using a template instance inside main() generates a free ride in terms of code and data size but moving the declaration to the global scope adds quite a bit to the code size even when the variable is declared static and still only used in main(). It makes no sense whatsoever.


I was wrong about the cause of the code size increase. I guess I should have realized that when I said that "it makes no sense".

GCC actually generates the template code I was expecting.

I will illustrate what was happening with a simplified example that doesn't use templates. It probably works in a similar way in C.

Consider a working program where all your global variables are uninitialized or initialized with immediate values. Add this to the global scope:

int x = 1;
int y = x;
int z = y;

The cost of adding x is 2 bytes of code.
The cost of adding y is 54 bytes of code!
The cost of adding z is 8 bytes of code.

The high cost of adding y is caused by pulling in startup code to carry out global initialization. It seems very likely that this is a cost incurred only once per program.

The bottom line for me is that I may be able to do what I want after all, using the previously described int-based workaround.

Sid

Life... is a state of mind

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

ChaunceyGardiner wrote:
Consider a working program where all your global variables are uninitialized or initialized with immediate values. Add this to the global scope:
int x = 1;
int y = x;
int z = y;

The cost of adding x is 2 bytes of code.
The cost of adding y is 54 bytes of code!
The cost of adding z is 8 bytes of code.

You may want to qualify x as const. If x is changed in the remainder, then use a const to initialize x.

In the following example for an ATmega128, g++ does not come up with ad-hoc constructors, but it will for x and y if pportb is not const qualified:

#include 

uint8_t volatile *const pportb = &PORTB;
uint8_t volatile *x = pportb;
uint8_t volatile *y = pportb+2;
uint8_t volatile *w = &PORTB+1;
uint8_t volatile **z = &x+1;

avrfreaks does not support Opera. Profile inactive.