Travis Griggs
Travis Griggs

Reputation: 22252

Why does one bitfield struct expression behave different across assignment?

Given an expression in C like:

foo = (<an expression>);

The compiler will often deign to assign the foo value multiple times. When foo is a register that controls hardware, that can lead to unexpected results. For example, I'm finding that expressions likes

foo = (struct Bar){.field1=13, .field2=42};

foo = Field1Value(13) | Field2Value(42);

will generate different sequences of updating foo. The second is generally pretty good about completing the rvalue and then assigning it. But the first often likes to update foo with multiple assignments. I have tried parentheses placement, but the optimizer seems to think otherwise.

UPDATE

What I wanted to understand is why these 3 statements had different results:

// 1
GCLK->CLKCTRL = (GCLK_CLKCTRL_Type){{.ID=GCLK_CLKCTRL_ID_TCC0_TCC1_Val, .GEN=GCLK_CLKCTRL_GEN_GCLK0_Val, .CLKEN=true}};
// 2
GCLK->CLKCTRL.reg = (GCLK_CLKCTRL_Type){{.ID=GCLK_CLKCTRL_ID_TCC0_TCC1_Val, .GEN=GCLK_CLKCTRL_GEN_GCLK0_Val, .CLKEN=true}}.reg;
// 3
GCLK_CLKCTRL_Type tmp = {{.ID=GCLK_CLKCTRL_ID_TCC0_TCC1_Val, .GEN=GCLK_CLKCTRL_GEN_GCLK0_Val, .CLKEN=true}};
GCLK->CLKCTRL = tmp;

The first was problematic. The second 2 work. I wanted to understand why. Obviously, the alternative way to do this kind of thing is with code like:

GCLK->CLKCTRL.reg = (GCLK_CLKCTRL_ID_TCC0_TCC1 | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_CLKEN);

The GCLK global is a pointer to this structure def:

typedef struct {
  __IO GCLK_CTRL_Type            CTRL;        /**< \brief Offset: 0x0 (R/W  8) Control */
  __I  GCLK_STATUS_Type          STATUS;      /**< \brief Offset: 0x1 (R/   8) Status */
  __IO GCLK_CLKCTRL_Type         CLKCTRL;     /**< \brief Offset: 0x2 (R/W 16) Generic Clock Control */
  __IO GCLK_GENCTRL_Type         GENCTRL;     /**< \brief Offset: 0x4 (R/W 32) Generic Clock Generator Control */
  __IO GCLK_GENDIV_Type          GENDIV;      /**< \brief Offset: 0x8 (R/W 32) Generic Clock Generator Division */
} Gclk

where the __IO is a macro for volatile among other things.

Upvotes: 1

Views: 112

Answers (2)

chux
chux

Reputation: 153478

How to force C compiler to complete computation before assignment (?)

It should be sufficient to insure foo is of type volatile. @ Eugene Sh.

volatile type_of_foo foo = Field1Value(13) | Field2Value(42);

An alternative would use 2 steps:

Assign the result to a safe, volatile, object that can be set many times, then assign to the register.

volatile type_of_foo foo_tmp = Field1Value(13) | Field2Value(42);

volatile type_of_foo foo = foo_tmp;

The compiler can assign, at the assembly code level, foo_tmp many times, yet cannot use that to assign foo until foo_tmp assignment is complete.

Pedantically, the foo = foo_tmp; itself, could in theory involve multiple assignments, but not likely. Consider _Atomic, an option C feature for more ideas.

Upvotes: 2

R.. GitHub STOP HELPING ICE
R.. GitHub STOP HELPING ICE

Reputation: 215259

For accessing memory-mapped registers, always use pointer-to-volatile types, and always ensure that the pointed-to type is a basic type that can be accessed as a single unit on your hardware architecture. In particular do not use assignment of whole struct-type objects and do not use bitfields, since the C language does not strictly specify how they interact with volatile. You can use a volatle-qualified struct whose individual members have types that make sense to access as a unit, though. For example:

struct regs {
    uint32_t r1;
    uint16_t r2, r3;
};
volatile struct regs *myregs = (volatile void *)0xadd13000;
myregs->r1 = x<<24 | y;
myregs->r2 = z;
myregs->r3 = w;

The reason this works is that the C language requires that the number of accesses performed via volatile objects be equal to the number that would be performed on the "abstract machine" defined by the language, and that their order with respect to each other honor the order they have on the abstract machine.

Upvotes: 2

Related Questions