Does it inline?
source link: https://bolinlang.com/does-it-inline
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Does it inline?
One of C and C++'s strengths is being able to inline a function and preform additional optimizations on it. Most of us have no idea when something will inline or not. Let's play a game called "Does It Inline?". Every round we'll show you multiple source files with a comment pointing out a difference and you'll have to guess if main will compile down to `return 0`. You can follow along by writing
g++ -march=native -O2 main.cpp && gdb -batch -ex 'file a.out' -ex 'disassemble main'
If you rather use lldb you can write lldb ./a.out -s myscript with myscript containing
disassemble -n main exit
Let's start off easy
Round 1:
inline int fn(int v) { return v; } int main(int argc, char *argv[]) { return fn(0); }
//remove inline int fn(int v) { return v; } int main(int argc, char *argv[]) { return fn(0); }
//move function body int fn(int v); int main(int argc, char *argv[]) { return fn(0); } int fn(int v) { return v; }
//extern C and non zero param extern "C" int fn(int v); int main(int argc, char *argv[]) { return fn(123); } int fn(int v) { return v*0; }
Result: All inline in both clang and gcc
Round 2:
Examples will be the header file, the source of all are
#include "header.h" int main(int argc, char *argv[]) { return fn(123); }
//Using a header without inline keyword int fn(int v) { return v*0; }
//using noinline __attribute__ ((noinline)) int fn(int v) { return v*0; }
//add the inline keyword __attribute__ ((noinline)) __inline int fn(int v) { return v*0; }
//Using both noinline and always_inline __attribute__ ((noinline)) __attribute__((always_inline)) __inline int fn(int v) { return v*0; }
//Same but swap __attribute__((always_inline)) __attribute__ ((noinline)) __inline int fn(int v) { return v*0; }
Result:
- clang and gcc both inline
- clang ignores the attribute and inline, gcc does not
- Neither inline, however gcc skips setting the function parameter
- same but now gcc warns that always_inline conflicts with noinline
- Both will inline. gcc warns that noinline conflicts with always_inline
To my surpise if you remove __inline clang will inline both C and D. Optimizers can be fickle
Round 3:
#include <stdlib.h> int main(int argc, char *argv[]) { return atoi("0"); }
//using strtol #include <stdlib.h> int main(int argc, char *argv[]) { return strtol("0", 0, 10); }
//using strtod which returns a double #include <stdlib.h> int main(int argc, char *argv[]) { return strtod("0", 0); }
Result: A: clang inline, gcc inline atoi but atoi calls strtol which isn't inlined. B) Same as previous C: Both call strtod
Round 4:
#include <vector> int main(int argc, char *argv[]) { std::vector<int> v; v.push_back(1000); return 0; }
//Added an if #include <vector> int main(int argc, char *argv[]) { std::vector<int> v; v.push_back(1000); if (v.size() > 0) { return 0; } return 1; }
//Added a pop inside the if and returned the size #include <vector> int main(int argc, char *argv[]) { std::vector<int> v; v.push_back(1000); if (v.size() > 0) { v.pop_back(); return v.size(); } return 1; }
Amazingly clang inline all of them including C. gcc doesn't inline any
Round 5:
class B { public: virtual int test() { return 0; }}; class D : public B { public: int test() override { return 1; }}; int main(int argc, char *argv[]) { D d; B*b = &d; auto p = dynamic_cast<D*>(b); return !p; }
//Add final to D class B { public: virtual int test() { return 0; }}; class D final : public B { public: int test() override { return 1; }}; int main(int argc, char *argv[]) { D d; B*b = &d; auto p = dynamic_cast<D*>(b); return !p; }
//Both branches are 0 class B { public: virtual int test() { return 0; }}; class D final : public B { public: int test() override { return 1; }}; int main(int argc, char *argv[]) { D d; B*b = &d; auto p = dynamic_cast<D*>(b); if (p) return 0; return 0; }
//replace the first return class B { public: virtual int test() { return 0; }}; class D final : public B { public: int test() override { return 1; }}; int main(int argc, char *argv[]) { D d; B*b = &d; auto p = dynamic_cast<D*>(b); if (p) return ((D*)p)->test()-1; return 0; }
//What happens if you change the cast to reinterpret_cast class B { public: virtual int test() { return 0; }}; class D final : public B { public: int test() override { return 1; }}; int main(int argc, char *argv[]) { D d; B*b = &d; auto p = reinterpret_cast<D*>(b); if (p) return ((D*)p)->test()-1; return 0; }
Result: Neither inline A and B. gcc inline C and D, both inline E
Round 6:
//main.cpp inline int getzero(); int main(int argc, char *argv[]) { return getzero(); } //getzero.cpp int getzero() { return 0; }
//A again but add in -flto
//Use -flto #include <stdlib.h> int main(int argc, char *argv[]) { return strtol("0", 0, 10); }
//For a laugh, recursive fibonacci int fibonacci(int n) { if(n == 0) return 0; else if(n == 1) return 1; else { return (fibonacci(n-1) + fibonacci(n-2)); } } int main() { return fibonacci(0); }
A: Both warn and use a jump. B: Using -flto both will inline C: gcc continues to not inline (even with -static) and clang had always inlined. D: Neither compilers are clever enough to see it doesn't need to call fibonacci
For the past few weeks we the Bolin team have been playing with an experimental feature, using a C header to inline C code into Bolin. It might make the next release
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK