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
    Dec 16 '18 at 0:15
  • @Jot, it's one of 3 until the unconditional else - it's the 2nd one to use a range match.
    – Patrick
    Dec 16 '18 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
    Dec 16 '18 at 2:39
  • 1
    don"t you need break in cases of the switch?
    – Juraj
    Dec 16 '18 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
    Dec 16 '18 at 13:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.