2

Code Snippet from a ARM linker file:

....
__RW_START_ = .;
    ...
    ...
    ...
    ...
__RW_END_ = .;

__RW_SIZE__ = __RW_END__ - __RW_START_;
....

Referring the variables RW_START, RW_END & RW_SIZE from above linker in the below C source file.

Code Snippet from a C source file:

....

#define _MAP_REGION_FULL_SPEC(_pa, _va, _sz, _attr, _gr)        \
        {                                                       \
                .base_pa = (_pa),                               \
                .base_va = (_va),                               \
                .size = (_sz),                                  \
                .attr = (_attr),                                \
                .granularity = (_gr),                           \
        }

#define MAP_REGION(_pa, _va, _sz, _attr)        \
        _MAP_REGION_FULL_SPEC(_pa, _va, _sz, _attr, REGION_DEFAULT_GRANULARITY)

#define MAP_REGION_FLAT(adr, sz, attr) MAP_REGION(adr, adr, sz, attr)


extern unsigned long __RW_START__;
extern unsigned long __RW_END__;
extern unsigned long __RW_SIZE__;

#define BL2_EL3_RW_START (const unsigned long)(&__RW_START__)
#define BL2_EL3_RW_SIZE (const unsigned long) ((&__RW_END__) - (&__RW_START__))   
//#define BL2_EL3_RW_SIZE (const unsigned long) (&__RW_SIZE__)                    
#define LS_BL2_EL3_RW_MEM               MAP_REGION_FLAT(BL2_EL3_RW_START, \
                                        BL2_EL3_RW_SIZE, \
                                        MT_DEVICE | MT_RW | MT_SECURE)
...
...

typedef struct mmap_region {
        unsigned long long      base_pa;
        uintptr_t               base_va;
        size_t                  size;
        mmap_attr_t             attr;
        size_t                  granularity;
} mmap_region_t;

const mmap_region_t plat_ls_mmap = LS_BL2_EL3_RW_MEM;  //compiler error
...
...

Question: Explain the below two observation:

  1. ARM Compiler gives following error when BL2_EL3_RW_SIZE is defined as above:

    error: initializer element is not constant .size = (_sz), \ ^

  2. ARM Compiler gives no error when BL2_EL3_RW_SIZE is defined as above(in commented lines).
  • I suggest you check the pre-processor output. – Paul Ogilvie Mar 20 '18 at 09:17
  • Note: after `.granularity = (_gr),` there is a comma. There shouldn't be one (end of initializer). – Paul Ogilvie Mar 20 '18 at 09:20
  • Note also that `RW_END` (I assume this is the end of a data segment) may not be known yet at the point of usage because not all variabes have been declared yet/not all modules linked. The error message may not be accurate then. – Paul Ogilvie Mar 20 '18 at 09:24
  • Btw, using variables prefixed with underscore might [result in UB](https://stackoverflow.com/a/25090719/69809). – vgru Mar 20 '18 at 13:16
  • @Groo, you mean using things like `__func__` or `__FILE__` and `__LINE__` lead to U.B. ??? – Luis Colorado Mar 21 '18 at 07:55
  • @LuisColorado: No, I was imprecise, sorry, declaring *your own* variables prefixed with underscore is UB, because these variables are reserved and *might* be used (for any use if there are two underscores or an upper case, or in file scope if it only has a single underscore followed by a lower case letter). This doesn't mean compiler will punish you for not reading the standard, but it means that you might face unexpected collisions with reserved identifiers. – vgru Mar 21 '18 at 08:09
  • @Groo, and what about if you are a library implementor ??? – Luis Colorado Mar 21 '18 at 19:27
  • @LuisColorado: if you are implementing one of the headers from the standard C library, then you can freely use them, yes. I presumed OP was not writing any of the standard headers. [Check the standard, sections 7.1.2 and 7.1.3.](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf) – vgru Mar 22 '18 at 17:04

2 Answers2

1

Well, for the first error you should have to post the exact argument passed to the macro at invocation, as probably you have passed a variable name as parameter and that requires code (executable) to retrieve the variable value and put it on the right place (this is what is not constant). By expanding the macros as used (you don't post any actual macro expansion, only the macro definitions) the expansion of LS_BL2_EL3_RW_MEM gives:

MAP_REGION_FLAT(BL2_EL3_RW_START, \
                                    BL2_EL3_RW_SIZE, \
                                    MT_DEVICE | MT_RW | MT_SECURE)

where the second parameter is a macro invocation that expands to

(const unsigned long) ((&__RW_END__) - (&__RW_START__))

which is not a constant expression, as it involves the evaluation of two long variable addresses (namely __RW_END__ and __RW_START__) and compute their difference (as a C expression). The C language only considers the & address operator as being able to construct a constant expression when the variable is global (as this is the case) as the addresses of dynamic variables and pointer dereferences are dynamic and non constant in nature. The problem here is that you have to substract the addresses of the two variables to form the initialization value, and that requires code (so no data inline can be used to initialize, only the calculation proper) cannot initialise a struct field with a non-constant expression. You have to use an assignment for that.

Even if having the cast to (const unsigned long), that doesn't imply the expression is a constant expression. What the const means here is that herein on, the expression value cannot be further modified, it should be considered a constant.... but that doesn't mean you are parsing a constant expression. A constant expression is one that can be solved at compile time (and indeed, an expression that depends on the value of two linker variables, cannot be determined at compile time, you'll agree with me on this, for sure) And in the present case, it is indeed true (that the expression is not constant) for the addresses of those variables, as the position of the two variables is not known until the linker has placed all segments inline and determines (in the second pass) the offset values and addresses of everything.

Further, even the value of __RW_SIZE__ (which is the thing computed in the macro) is not a constant expression. It is computed at link time, and so you cannot determine the result value until you have finished compiling. The macro purpose is only for the case you don't have access to the variable.

Finally, the last case (the compiler not giving a complaint when the value used is the address of the __RW_SIZE__ variable) is that then, the expression is a constant... the constant is precisely the address of a global variable. This is determined at link time, but with a constant, fixed value, that has not to be calculated. But you have to think on one thing... the value of __RW_SIZE__ is the value you need, and not it's address, so if you initialise the struct field with the address of __RW_SIZE__, then you are making a mistake, as the address and the value of __RW_SIZE__ are two different things (indeed, if you substitute the value of __RW_SIZE__ into the initializer, you'll see the same error as in the first case, because the contents of __RW_SIZE__ are not know at compilation time.)

WHAT THE HELL IS A CONSTANT EXPRESSION

Constant expression (compile time) is an expression that can fully be computed at compile time (not something labeled with a const keyword) The compiler allows you to put an expression to facilitate things, but the compilation of such an initializer is always a constan value. This means that, if you put something like:

int a = 3 + 2;

the compiler will reserve space for a variable and fill the .data segment with four bytes and the constant 5 on it (the compiler calculates the value, instead of generating code to calculate it)

This makes things like

#define RAD_TO_DEGREES (180.0 / MATH_PI)

to be possible initializers, as the compiler can calculate the division and initialize the variable RAD_TO_DEGREES to the proper constant.

But if you put something like:

double SIN_OF_30 = cos(30.0 / RAD_TO_DEGREES);

let's see what happens.... the compiler interprets the expression and calculates the argument to the sin function first. RAD_TO_DEGREES happens to be 57.2957795 which is known at compile time (be careful, it isn't if you have defined a variable with that value and initialised with it, and you use the variable in the calculation, as the variable has only that value is something that only you know, the compiler doesn't). This leads to the argument value of 0.5235988, but then comes the problem. The sin(3) function is a mathematical function that comes from the mathematical standard library, so the compiler has to execute it to compute the final value of 0.5, so this is NOT A CONSTANT EXPRESSION, even when the final variable has defined as const. Only if you avoid to pass through sin() you have a constant expression. So you can use

double SIN_OF_30 = 0.5;
double COS_OF_30 = 0.8660;

but not

double SIN_OF_30 = sin(30.0 / RAD_TO_DEG);
double COS_OF_30 = cos(30.0 / RAD_TO_DEG);

for automatic variables, this doesn't hold, as the initialization is done at run time, so the compiler generates the neccessary code to initialize them

int main()
{
    double SIN_OF_30 = sin(30.0 / RAD_TO_DEG);
    double COS_OF_30 = cos(30.0 / RAD_TO_DEG);
}

is perfectly valid. Each time you enter the main function, two variables are created in the stack, and initialised with the values result of the expressions you put there (which don't have to be constant, compile-time expressions).

Community
  • 1
  • 1
Luis Colorado
  • 10,974
  • 1
  • 16
  • 31
  • Thanks Luis for such a detailed explanation. It really helped. – Pankaj Gupta Mar 21 '18 at 03:50
  • I have a follow-up question on your explanation. Refer the below code snippet where two integer variable 'a' & 'b' are assigned value based on user input. Later, the difference of the two is assigned to a const variable 'c'. This code do not give compilation error and refer the output that const variable 'c' is correctly assigned based on the user input values of 'a' & 'b'. – Pankaj Gupta Mar 21 '18 at 06:11
  • #include void main(void) { int a; int b; printf("Value of A = %d\n", a); printf("Value of B = %d\n", b); scanf("%d %d", &a, &b); printf("Value of A = %d\n", a); printf("Value of B = %d\n", b); const int c = a - b; printf("Value of C = %d\n", c); } Output is: Value of A = 0 Value of B = -1234393408 1 1 Value of A = 1 Value of B = 1 Value of C = 0 – Pankaj Gupta Mar 21 '18 at 06:11
  • @PankajGupta, sorry, can you explain what you have posted and why? – Luis Colorado Mar 21 '18 at 06:56
  • just change `c` declaration to `static` and you'll see the error arising again. `static` (or global) initializations require that **initializers be compile time constant expressions** (not read-only variables protected by `const` keyword), because global variables are initialised from a static data segment (no code, no expressions to calculate values, only initialization values) so the expression must be calculable at compile time. This doesn't apply to initialisers of automatic variables, I'm afraid. – Luis Colorado Mar 21 '18 at 07:11
  • .... that means that you can use as initialiser something like `3 + 2` or `6`, but not `3 + a` (being `a` a variable) or `3.0 + cos(MATH_PI)` **because calculation of the final value implies generating code to calculate it**, and global and `static` variables don't receive initialisers from executing code. – Luis Colorado Mar 21 '18 at 07:17
  • ... and don't interpret an initialiser as an assignment, it isn't. `static int c = a + b; ` would fail also, as the initialiser is not a constant expression (even being `c` non-`const`) This happens because the initial value of `c` cannot be known at compile time, and the data segment cannot be fully fulfilled without executing the expression to compute the value. I think you are confounding `const` attribute (which is an attribute of a value) with a constant expression (which is something you can substitute directly in the code with the result value. – Luis Colorado Mar 21 '18 at 07:27
1

First of all, the problem you are having has nothing to do with linker file variables.

When you initialize a file-local variable, its initial value must be a constant expression. So your "observation 1" is perfectly logical. The main question is, why the second observation does not give an error.

The answer is relocation. The compiler is allowed to extend what "constant expression" means, and most compilers accept relocations as constant expressions.

Here is a little code snippet with comments:

// Note: "const" here means "read-only", not "constant expression"
const unsigned long var1, var2;

// This is not allowed, initializers must be constant expressions
unsigned long test1 = var1;

// This however is supported by compiler using relocation as a constant expression
unsigned long test2 = (unsigned long)&var1;

// This is supported by using relocation with addend
unsigned long test3 = (unsigned long)&var1 + 10;

// This is not supported by relocation - there is no relocation type that supports division with addend
unsigned long test4 = (unsigned long)&var1 / 10;

// This is not supported by relocation - addend itself cannot be relocation
unsigned long test5 = (unsigned long)&var1 + (unsigned long)&var2;


int main (int argc, char **argv)
{
    // Yes, this is how we do it...
    unsigned long test6 = (unsigned long)&var1 + (unsigned long)&var2;

    return 0;
}

Now, to solve your initial problem - even though #define BL2_EL3_RW_SIZE (const unsigned long) (&__RW_SIZE__) compiles, doesn't mean it gives you the expected result. It only uses the address of the __RW_SIZE__ to initialize the struct member and the address of the __RW_SIZE__ is the same as the address of __RW_END__. You cannot get the value of linker variable from C code.

Only way you can achieve what you want is initialize the variable during run-time by using some init() function and doing plat_ls_mmap.size = &__RW_END__ - &__RW_START__;

Erki Aring
  • 2,032
  • 13
  • 15