Why is there a difference in generating a jump-table and code for a switch and equivalent if-statement

Viewed 118

By the below link to Compiler Explorer you get two versions of the same finite state machine: one as a switch-statement

#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>

#define MORE
//#define ENTER

volatile uint8_t r = 0;
volatile uint8_t x = 0;

namespace {
    struct FSM {
        enum class State : uint8_t {Test0, Test1, Test2, Test3, Test4
                        #ifdef MORE
                                    , Test5, Test6, Test7
                        #endif
                                   };
        
        inline static void process(const uint8_t b) {
            const auto oldState = mState;
            switch(mState) {
            case State::Test0:
                if (b == 0) {
                    mState = State::Test1;
                }
                break;
            case State::Test1:
                if (b == 1) {
                    mState = State::Test2;
                }
                break;
            case State::Test2:
                if (b == 2) {
                    mState = State::Test3;
                }
                break;
            case State::Test3:
                if (b == 3) {
                    mState = State::Test4;
                }
                break;
            case State::Test4:
                if (b == 4) {
#ifdef MORE
                    mState = State::Test5;
#else
                    mState = State::Test0;
#endif
                }
                break;
#ifdef MORE
            case State::Test5:
                if (b == 5) {
                    mState = State::Test6;
                }
                break;
            case State::Test6:
                if (b == 6) {
                    mState = State::Test7;
                }
                break;
            case State::Test7:
                if (b == 7) {
                    mState = State::Test0;
                }
                break;
#endif
            }
#ifdef ENTER
            if (oldState != mState) {
                switch(mState) {
                case State::Test0:
                    x = 40;
                    break;
                case State::Test1:
                    x = 41;
                    break;
                case State::Test2:
                    x = 42;
                    break;
                case State::Test3:
                    x = 43;
                    break;
                case State::Test4:
                    x = 44;
                    break;
#ifdef MORE
                case State::Test5:
                    x = 45;
                    break;
                case State::Test6:
                    x = 46;
                    break;
                case State::Test7:
                    x = 47;
                    break;
#endif
                }
            }
#endif
        }
    private:
            inline static State mState{State::Test0};
    };
    
    using fsm = FSM;
}

int main() {
    while(true) {
        const uint8_t b = r;        
        fsm::process(b);
    }
}

and one as equivalent if-statments.

#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>

#define MORE
//#define ENTER

volatile uint8_t r = 0;
volatile uint8_t x = 0;

namespace {
    struct FSM {
        enum class State : uint8_t {Test0, Test1, Test2, Test3, Test4
                        #ifdef MORE
                                    , Test5, Test6, Test7
                        #endif
                                   };
        
        inline static void process(const uint8_t b) {
            const auto oldState = mState;
            if (mState == State::Test0) {
                if (b == 0) {
                    mState = State::Test1;                
                }
            }
            else {
                if (mState == State::Test1) {
                    if (b == 1) {
                        mState = State::Test2;
                    }
                }
                else {
                    if (mState == State::Test2) {
                        if (b == 2) {
                            mState = State::Test3;
                        }
                    }
                    else {
                        if (mState == State::Test3) {
                            if (b == 3) {
                                mState = State::Test4;
                            }
                        }
                        else {
                            if (mState == State::Test4) {
                                if (b == 4) {
#ifndef MORE
                                    mState = State::Test0;                                
#else
                                    mState = State::Test5;                                
                                }
                            }
                            else {
                                if (mState == State::Test5) {
                                    if (b == 5) {
                                        mState = State::Test6;
                                    }
                                }
                                else {
                                    if (mState == State::Test6) {
                                        if (b == 6) {
                                            mState = State::Test7;
                                        }
                                    }
                                    else {
                                        if (mState == State::Test7) {
                                            if (b == 7) {
                                                mState = State::Test0;
                                            }
                                        }
                                    }
#endif
                                }
                            }
                        }
                    }
                }
            }
#ifdef ENTER
            if (oldState != mState) {
                if (mState == State::Test1) {
                    x = 41;                    
                }
                else {
                    if (mState == State::Test2) {
                        x = 42;                        
                    }
                    else {
                        if (mState == State::Test3) {
                            x = 43;                            
                        }
                        else {
                            if (mState == State::Test4) {
                                x = 44;                                
                            }
#ifdef MORE
                            else {
                                if (mState == State::Test5) {
                                    x = 45;                                    
                                }
                                else {
                                    if (mState == State::Test6) {
                                        x = 46;                                        
                                    }
                                    else {
                                        if (mState == State::Test7) {
                                            x = 47;                                        
                                        }                                        
                                    }
                                }
                            }
#endif
                        }
                    }
                }
            }
#endif
    }
    private:
        inline static State mState{State::Test0};
    };
    
    using fsm = FSM;
}

int main() {
    while(true) {
        const uint8_t b = r;        
        fsm::process(b);
    }
}

https://godbolt.org/z/7TdEo31KE

Side note: this is a stripped down version of some tests I made to implement the finite-state-machine with template-metaprogramming, which is quite straight forward and fundamentally gives the same copy as the if-statement example by template-recursion.

First I found that gcc starts to generate a jump-table for the switch-implementation using more than 5 states, the same occurs in the if-statement example with more than 7 states.

But even with more than 7 states the code is fundamentally different: the if-statement example uses a 7-entry jump-table whereas the switch-statement example uses a jump-table with 8 entries. This is also true for x86 backend and also for clang.

My assembler skills are limited, therefore two questions arise:

  1. which one is more performant?
  2. can some changes to the code make the two version look the same in assembler?
0 Answers
Related