EDIT: The problem is my op-code catch is op >= 0x20 || op <= 0x2F rather than op >= 0x20 && op <= 0x2F and so most of the switch-case was being optimised away!

This is a really difficult question to ask, because I cannot make a minimal-verifiable version of it as its own sketch.

For fun, I'm writing an ATtiny85 byte-code interpreter. It reads op codes from EEPROM and executes them. Keeping my eye on the program size, I've been completely stumped by what's happening in the following.
I really must stress though - I could not recreate this in a minimal sketch :(

//_mem[] is a uint8_t[] for RAM allocation
//_pMem is a moveable pointer to _mem[]
//NextByte() retreives the next byte from EEPROM

//Before this is if-else statements catching ranges of similar op codes
//This one catches similar math functions (mem op= num1):
  } else if (op >= 0x20 || op <= 0x2F) {
    uint8_t num1 = _mem[NextByte()];
    if      (op == 0x20) *_pMem += num1; //1A
    else if (op == 0x21) *_pMem -= num1; //1S

//For all other op codes:
  } else {

//These can mostly be ignored

    switch (op) {
      case 0x00: J(*_pMem);                                break; //J_
      case 0x01: _o = NextWord();                          break; //JA
      case 0x0A: ++_pMem;                                  break; //MI
      case 0x0B: --_pMem;                                  break; //MD
      case 0x0C: _pMem += NextByte();                      break; //MA
      case 0x0D: _pMem -= NextByte();                      break; //MS
      case 0x10: *_pMem = NextByte();                      break; //S_
      case 0x11: _mem[NextByte()] = _mem[NextByte()];      break; //CP
      case 0x12: *_pMem = _mem[NextByte()];                break; //V_
      case 0x13: _mem[NextByte()] = *_pMem;                break; //_V
      case 0x80: SV();                                     break; //SV
      case 0xFF: noHalt = false;                           break; //HT

The program in total, when in this configuration, is 2030 bytes.
However, when I instead put just the two op codes, 0x20 and 0x21, into the switch-case, the program balloons to 2680 bytes! Code:

      //the if-else statement for 0x20 and 0x21 is completely removed
      case 0x13: _mem[NextByte()] = *_pMem;                break; //_V
      case 0x20: *_pMem += _mem[NextByte()];               break; //1A
      case 0x21: *_pMem -= _mem[NextByte()];               break; //1S
      case 0x80: SV();                                     break; //SV

Immediately, I thought "it must be something to do with replacing num1 with _mem[NextByte()] each time", but when I tried the same thing in the if-else configuration, it was only 2044 bytes.

Obviously there's waaay more code besides this out-of-context slice, and I'd be happy to share the whole sketch, but why does it even matter? I cannot imagine why there is a 600+ byte jump, other than maybe the logic behind the switch(). Hence I'm asking!
It's not as if it's suddenly using floating point or any other gotcha...

While trying to make a minimal sketch, the if-else and switch statement configurations were only several bytes different in size, even while propping up various parts with unoptimisable code.

Thank you for all your time.

  • I can only guess, but my guess is that those two lines break the optimization (perhaps a number of optimizations). Perhaps not just the switch-case but the memory pointers as well. The switch-case can be efficient and it can be not efficient. Therefor, you can only get this behaviour with the switch-case statement. Perhaps there are compiler options that can be useful, but I don't know any for the switch-case. What was the size with a if-else chain with and without those two lines?
    – Jot
    Commented Dec 16, 2018 at 0:15
  • @Jot, it's one of 3 until the unconditional else - it's the 2nd one to use a range match.
    – Patrick
    Commented Dec 16, 2018 at 1:17
  • I meant, if you convert the complete switch-case to a if-else-if chain, then what is the difference in code size with and without those two lines? That should be normal. A if-else-if chain is compiled in a normal way, the switch-case is known to cause such things, although 600 bytes is really a lot. I can't believe that the compiler generates a full table (or two tables) with a jump table for 256 16-bit addresses. If removing the switch-case helps, then stay away from it. You could have a look at the binary code with avr-objdump.
    – Jot
    Commented Dec 16, 2018 at 2:39
  • 1
    don"t you need break in cases of the switch?
    – Juraj
    Commented Dec 16, 2018 at 7:04
  • 2
    Sorry to waste everybody's time! We can see exactly what's happened... my op-code catch is op >= 0x20 || op <= 0x2F rather than op >= 0x20 && op <= 0x2F, so the whole switch-case was being optimised away.
    – Patrick
    Commented Dec 16, 2018 at 13:44


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.