Assembly-code generated by the IDE: whether it looks suspect?

Hello All,
Greetings.
This is my first project using the arduino. And The code/machine doesnot behave as intended. So I compiled in 'verbose' mode, 'located' the elf file generated by the IDE, and disassembled (using avr-objdump) it to inspect the code generated by the cross-compiler.

For the method that is defined as under:

void operateBottleStopper (char bottle, String oc) {
  if (oc.equals("open")) {
    if (bottle == 'l') {
      digitalWrite (9,  HIGH);
      digitalWrite (8,  HIGH);
    } else {
      digitalWrite (15, HIGH);
      digitalWrite (14, HIGH);
    }
  } else {
    if (bottle == 'l') {
      digitalWrite (9,  LOW);
      digitalWrite (8,  LOW);
    } else {
      digitalWrite (15, LOW);
      digitalWrite (14, LOW);
    }
  }
  delay (200);
}

------method defination ends here -----

I discover that the corresponding assembly code reads as follows:

00000962 <_Z20operateBottleStopperc6String>:
     962:       cf 93           push    r28
     964:       c8 2f           mov     r28, r24
     966:       cb 01           movw    r24, r22
     968:       63 e4           ldi     r22, 0x43       ; 67
     96a:       71 e0           ldi     r23, 0x01       ; 1
     96c:       0e 94 62 04     call    0x8c4   ; 0x8c4 <_ZNK6String6equalsEPKc>
     970:       88 23           and     r24, r24
     972:       b9 f0           breq    .+46            ; 0x9a2 <_Z20operateBottleStopperc6String+0x40>
     974:       61 e0           ldi     r22, 0x01       ; 1
     976:       cc 36           cpi     r28, 0x6C       ; 108
     978:       71 f4           brne    .+28            ; 0x996 <_Z20operateBottleStopperc6String+0x34>
     97a:       89 e0           ldi     r24, 0x09       ; 9
     97c:       0e 94 de 00     call    0x1bc   ; 0x1bc <digitalWrite>
     980:       61 e0           ldi     r22, 0x01       ; 1
     982:       88 e0           ldi     r24, 0x08       ; 8
     984:       0e 94 de 00     call    0x1bc   ; 0x1bc <digitalWrite>
     988:       68 ec           ldi     r22, 0xC8       ; 200
     98a:       70 e0           ldi     r23, 0x00       ; 0
     98c:       80 e0           ldi     r24, 0x00       ; 0
     98e:       90 e0           ldi     r25, 0x00       ; 0
     990:       cf 91           pop     r28
     992:       0c 94 62 01     jmp     0x2c4   ; 0x2c4 <delay>
     996:       8f e0           ldi     r24, 0x0F       ; 15
     998:       0e 94 de 00     call    0x1bc   ; 0x1bc <digitalWrite>
     99c:       61 e0           ldi     r22, 0x01       ; 1
     99e:       8e e0           ldi     r24, 0x0E       ; 14
     9a0:       f1 cf           rjmp    .-30            ; 0x984 <_Z20operateBottleStopperc6String+0x22>
     9a2:       60 e0           ldi     r22, 0x00       ; 0
     9a4:       cc 36           cpi     r28, 0x6C       ; 108
     9a6:       29 f4           brne    .+10            ; 0x9b2 <_Z20operateBottleStopperc6String+0x50>
     9a8:       89 e0           ldi     r24, 0x09       ; 9
     9aa:       0e 94 de 00     call    0x1bc   ; 0x1bc <digitalWrite>
     9ae:       60 e0           ldi     r22, 0x00       ; 0
     9b0:       e8 cf           rjmp    .-48            ; 0x982 <_Z20operateBottleStopperc6String+0x20>
     9b2:       8f e0           ldi     r24, 0x0F       ; 15
     9b4:       0e 94 de 00     call    0x1bc   ; 0x1bc <digitalWrite>
     9b8:       60 e0           ldi     r22, 0x00       ; 0
     9ba:       f1 cf           rjmp    .-30            ; 0x99e <_Z20operateBottleStopperc6String+0x3c>

-------- excerpts from the disassembled elf file contents ends here --------

I notice that whereas the c-code is invoking the "digitalWrite" in 8 places, the generated code is invoking it in only 5 places ! On the face of it, does the translation look suspect to informed members of the community ? Also any advice/suggestions/corrections? I am using the Arduino IDE Version 1.8.8.

Thanks, and kind regards,
Vivek.

It also appear to be calling delay, which your source doesn't.

Are you certain this is for the same code?

Edit: didn't scroll far enough.
Ignore.

Maybe there's an optimisation that isn't obvious from the snippet posted.

I'll take a look when I get back to a computer.

if you are going to understand the output in assembler why dont you program directly in assembler is very easy for that kind of program you wrote....

The gcc does heavy optimizing of your code. So it isn't easy to compare the assembly code to your sketch.
Depending on your if's you call digital write allways 2 times with different parameters. So the last calls in your function is allways a digitalWrite( with different parameters ) followed by a delay. This last digitalWrite is called only once in the assembly, but with different register settings. That's why you see only 5 calls of digitalWrite in the assembly code.

It is very very unlikely, that you will find an error in GCC. If your sketch doesn't do as you expect it, in 99,999999% there is an error in your code.

Just to explain that the code you write and the code you generate could be different, this is another version of your code that the compiler+optimizer could decide to go for:

void operateBottleStopper (const char bottle, const String& oc) {
  uint8_t o = oc.equals("open");
  uint8_t l = bottle == 'l';

  if (l) {
    digitalWrite (8,  o);
    digitalWrite (9,  o);
  } else {
    digitalWrite (15, o);
    digitalWrite (14, o);
  }
  delay (200);
}

then only 4 digitalWrite

The truth is that for me, the last place I would look for an explanation about why something I wrote isn't what the program is doing.…

the last place I would look…

like the very last place, is the compiler output assembly language.

Please post a complete sketch that doesn't do what you think it should.

Imma bet it's a PEBKAC, or the variant ID10T.

a7

1 Like

I think it's optimizing along the lines of

void operateBottleStopper (char bottle, String oc) {
  temp = LOW;
  if (oc.equals("open")) {
    temp = HIGH;
  }
  if (bottle == 'l') {
    digitalWrite (9,  temp);
    digitalWrite (8,  temp);
  } else {
    digitalWrite (15, temp);
    digitalWrite (14, temp);
   }
}

Not sure why you ended up with five digitalWrite() rather than only four.

Yep, as suggested in #5

The optimizer might notice HIGH and LOW are similar in value to true and false as implemented and just skip the test truth value

Indeed weird there are 5 calls, would need to make sure this relates to the actual code snippets we got

Because the compiler didn't optimize that way. The compiler optimized at assembly level. There isn't a way to express this in c++ code.

Of course there is (will require a goto) . Just need to make sense out of what was generated (which is convoluted)

No definitely not. You cannot express that kind of optimization in C++. Of course the assembly code does what the C++ code says it does. But you cannot express that kind of optimization by changing or optimizing your C++ code.
There is no direct relationship between C++ statements and assembly code anymore. Some assembly statements belong to more than one C++ statements. E.G. this is here with the digitalWrite call.
Several assembly statements are needed for one C++ digitaWrite call: Setting registers to the parameter values and then calling the assembler subroutine. This is optimized by the compiler with different register settings, but only one call of digitalWrite subroutine. You cannot express this kind of optimization in C++ because the C++ statements are mixed up at assembly level. That is why you see less 'call digitalWrite' in assembly then digitalWrite statements in C++.

I meant algorithmically you can write in C/C++ what the assembly language does. I’m not saying it will generate the same assembly if you compile it).

From the look of it It’s just function calls, variable (registers) settings, testing and branching, sometimes to specific places within the code hence the need for goto.

Usually it's the other way round :wink: . And so if you look at the generated assembly code, you already have written in C++ what this code does :joy:

@westfw showed an optimized version at C++ level and still wondered why the assembly code contained 5 calls to digitalWrite. And that's what I meant: Optimizing at C++ level is different from the optimizing at assembly level.
Maybe we're just talking past each other :innocent:

The optimisations of the GCC is a very complex topic

Yes of course. I meant that as an exercise to understand why the 5 calls and how the compiler got there.

(@westfw code is kinda the same I showed earlier in post 5 as well with 4 calls if you missed it.)

1 Like

Would even be interesting to see the generated assembly code of this version...

Yes (I’m just on my iPhone so can’t really dig into this at the moment) but I’m curious

Hello again.
Thanks for all your responses. They helped. Indeed the translation has/had no problem. I should have known better than to suspect that. But ...

After a few weeks of more work, we managed to get the implementation working as per expectation. In the process we found and fixed many errors, that i am listing below for completeness. Hope it will be of value to other members of the community.

  1. Our implementation used a previous generation 8-relay module that did not include the opto-isolation capability. Possibly, a proper use of that module would have required an additional resistor in the control-signal path that we had not
    installed. We replaced this module with a current-generation module that includes the opto-isolation feature.
  2. Missing port-initialisation: Our implementation was numbering, and assigning the input/output functionality to the ports properly. But it was not indicating as to whether the output ports have to be initialised to HIGH/LOW. This was corrected.
  3. Our earlier wiring of the Jd-Vcc pin of the relay-module not correctly done.
    This was corrected.
  4. Incorrect grounding! Our earlier implementation of grounding was in error. The correction is simply stated: the devices powered from a power-source must be grounded there-from. There need-not, and will-not be a common ground.

After correcting as described above, things worked mostly well, except for an occassionnal reset. Also the reset (if it happens) would happen at very specific events. The inconvinience caused by this was worked around by using the EEPROM memory to avoid the need for "initialisation" after the unnecessary "reset" occurrances.

Again, thanks all, for your help.
kind regards,
Vivek.

It looks like the compiler noticed that all four paths through the code end in:

    digitalWrite();
    digitalWrite();
    delay(200);
    return;

If uses one digitalWrite() for the second and final digitalWrite() in each path and four more for the first digitalWrite() in each path. In each path it sets r24 to the first argument (pin number) and r22 to the second argument (value), calls digitalWrite(), then sets r22 and/or r24 for the final call to digitalWrite() and jumps to it.

Sort of like:

  int r24, r22;
  if (oc.equals("open"))
  {
    r22 = 1;
    if (bottle == 'l')
    {
      r24 = 9;
      digitalWrite (r24,  r22);
      r24 = 8;
    }
    else // bottle != 'l'
    {
      r24 = 15;
      digitalWrite (r24,  r22);
      r24 = 14;
    }
  }
  else // !oc.equals("open")
  {
    r22 = 0;
    if (bottle == 'l')
    {
      r24 = 9;
      digitalWrite (r24,  r22);
      r24 = 8;
    }
    else // bottle != 'l'
    {
      r24 = 15;
      digitalWrite (r24,  r22);
      r24 = 14;
    }
  }
  digitalWrite (r24,  r22);
  delay (200);

That explains why there are 5 instead of 8.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.