Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compile error with Intel C++ compiler and "constexpr" #2736

Closed
iskunk opened this issue Oct 28, 2019 · 15 comments
Closed

Compile error with Intel C++ compiler and "constexpr" #2736

iskunk opened this issue Oct 28, 2019 · 15 comments

Comments

@iskunk
Copy link

iskunk commented Oct 28, 2019

Environment

  • Tesseract Version: 4.1.0
  • Commit Number: N/A (using release tarball)
  • Platform: GNU/Linux on x86-64

Current Behavior:

I am compiling Tesseract using the Intel C++ compiler, version 19.0.1.144, and encounter the following build error:

functions.cpp(42): error: calling the default constructor for "tesseract::LUTTempl<4096, tesseract::LUTFuncTanh>" does not produce a constant value
  constexpr LUTTempl<kTableSize, LUTFuncTanh> TanhTable;
                                              ^
functions.h(48): note: cannot call non-constexpr function "tanh" (declared at line 77 of "/usr/include/bits/mathcalls.h")
    return std::tanh(i / kScaleFactor);
           ^
functions.h(59): note: called from:
        table_[i] = f(i);
                     ^

functions.cpp(43): error: calling the default constructor for "tesseract::LUTTempl<4096, tesseract::LUTFuncLog>" does not produce a constant value
  constexpr LUTTempl<kTableSize, LUTFuncLog>  LogisticTable;
                                              ^
functions.h(52): note: cannot call non-constexpr function "exp" (declared at line 101 of "/usr/include/bits/mathcalls.h")
    return 1 / (1 + std::exp(-i / kScaleFactor));
                    ^
functions.h(59): note: called from:
        table_[i] = f(i);
                     ^

compilation aborted for functions.cpp (code 2)

This is with -std=c++17. The following edits allow source compilation to proceed:

diff -ru ./functions.cpp tesseract-4.1.0/src/lstm/functions.cpp
--- tesseract-4.1.0/src/lstm/functions.cpp.orig	2019-07-07 08:34:08.000000000 -0400
+++ tesseract-4.1.0/src/lstm/functions.cpp	2019-10-28 14:53:28.556054000 -0400
@@ -39,8 +39,8 @@
 
 #else // C++14 or newer
 
-constexpr LUTTempl<kTableSize, LUTFuncTanh> TanhTable;
-constexpr LUTTempl<kTableSize, LUTFuncLog>  LogisticTable;
+/*constexpr*/ LUTTempl<kTableSize, LUTFuncTanh> TanhTable;
+/*constexpr*/ LUTTempl<kTableSize, LUTFuncLog>  LogisticTable;
 
 #endif
 
diff -ru ./functions.h tesseract-4.1.0/src/lstm/functions.h
--- tesseract-4.1.0/src/lstm/functions.h.orig	2019-07-07 08:34:08.000000000 -0400
+++ tesseract-4.1.0/src/lstm/functions.h	2019-10-28 14:53:08.956078000 -0400
@@ -65,8 +65,8 @@
   double table_[n];
 };
 
-extern const LUTTempl<kTableSize, LUTFuncTanh> TanhTable;
-extern const LUTTempl<kTableSize, LUTFuncLog>  LogisticTable;
+extern /*const*/ LUTTempl<kTableSize, LUTFuncTanh> TanhTable;
+extern /*const*/ LUTTempl<kTableSize, LUTFuncLog>  LogisticTable;
 
 #endif

Alternately, compiling with -std=c++11 also allows compilation to succeed.

Expected Behavior:

This issue appears similar to #2246, but I do not believe it is the result of a shortcoming in the Intel compiler. (The compiler supports up to C++17, and in our experience has been stricter about conformance than G++.)

Suggested Fix:

I am not familiar with this C++ usage, but will be happy to test any changes.

@stweil
Copy link
Member

stweil commented Oct 29, 2019

If the else code fails with this compiler, using the if code seems to be a simple solution. We just need to extend the if condition to include this special compiler. Perhaps you can do that yourself and send a pull request?

Removing the const attributes would not be a good solution because they help the compilers to produce faster code and allow to calculate the table at compile time.

@amitdo
Copy link
Collaborator

amitdo commented Oct 29, 2019

If 2 compilers (clang, icc) does not accept that code, maybe it's wrong?

@iskunk
Copy link
Author

iskunk commented Oct 29, 2019

I'm no C++ guru, and the information I've found online on this topic is about as clear as mud. This is the closest I've got:

https://stackoverflow.com/questions/27744079/is-it-a-conforming-compiler-extension-to-treat-non-constexpr-standard-library-fu

However, it does appear that there can be cases where a constexpr function is ill-formed, yet the standard requires no diagnostic, so the fact that the code compiles doesn't necessarily mean it is completely correct. (Which may have implications for the optimization it is intended to bring about.)

@amitdo
Copy link
Collaborator

amitdo commented Oct 29, 2019

@amitdo
Copy link
Collaborator

amitdo commented Oct 29, 2019

@amitdo
Copy link
Collaborator

amitdo commented Oct 29, 2019

#if __cplusplus < 201402 || defined(__clang__) // C++11

#if __cplusplus < 201402 || defined(__clang__) || defined(__INTEL_COMPILER) // C++11

stweil added a commit that referenced this issue Oct 30, 2019
Signed-off-by: Stefan Weil <sw@weilnetz.de>
@stweil
Copy link
Member

stweil commented Oct 30, 2019

Thank you. Fixed in commit ca87b06.

@zdenop zdenop closed this as completed Oct 30, 2019
@iskunk
Copy link
Author

iskunk commented Oct 30, 2019

I don't understand. Is the code correct, and both the Clang and Intel compilers are erroring out spuriously? (I can submit an issue to Intel, if need be.)

It's not clear that this C++ usage is correct, and the Stack Overflow link that @amitdo posted implies that G++ accepts it only due to a non-conforming extension. If the desire is to take advantage of a GNU extension, then the code at issue should be enclosed in a #ifdef __GNUG__ or similar conditional, rather than enumerating all compilers which do not support it. However, I doubt that this should be necessary; do C++14/C++17 really not allow an acceptable form of this initialization?

@stweil
Copy link
Member

stweil commented Oct 30, 2019

I'd welcome code which works with any modern compiler and which does the same.

The goal is to calculate two lookup tables at compile time instead of wasting time during execution.

This works perfectly with the GNU compiler:

    $ size functions.o
       text	   data	    bss	    dec	    hex	filename
      65536	      0	      0	  65536	  10000	lstm/functions.o

Here the result with clang which needs code to fill the tables at runtime:

    $ size functions.o
      text	   data	    bss	    dec	    hex	filename
       200	      8	  65537	  65745	  100d1	lstm/functions.o

@iskunk
Copy link
Author

iskunk commented Oct 30, 2019

I completely agree that such an optimization is desirable. The problem is, the current implementation is not valid C++14/C++17 code, and only works with G++ due to a GNU extension---yet this is conditionalized in the code as though it were a limitation of the Clang and Intel compilers. If and when someone tries to build Tesseract using some other modern/conforming C++ compiler, they're going to encounter the same issue, and by this pattern yet another || defined(__BLAH__) will end up getting added. Eventually, you could well have every modern C++ compiler other than G++ listed here.

I don't know C++ well enough to suggest the Right Way(tm) of doing this initialization (after some more reading, it's not clear to me that such a way even exists at this point in time), but I would say, if you're going to rely on a GNU extension, then own up to it---conditionalize that code so that only __GNUG__ sees it, and everything else gets the less-optimal (but conformant) version. If and when other compilers are willing to break the standard like the GNU folks have, then those can be added to the conditional. It'll surely be a smaller set than where the current one is headed.

@stweil
Copy link
Member

stweil commented Oct 30, 2019

In the last hour I have read a bit more on this topic. The code is valid C++14 code for all compilers which handle the functions exp and tanh with a known argument as constexpr. The GNU compiler does this while other compilers don't. I think it is legitimate to say this is a limitation of such compilers.

The current implementation with the growing list of cases which don't use the optimal code is the result of a software evolution. We want the optimal code. If we'd change the implementation to restrict the optimal code to g++, we might miss other compilers which support it. And we would miss the fact that some compilers don't use the optimal code.

To solve this dilemma, we could add a feature check to the configure script. Or we could generate the source code for constant lookup tables, either by a small Python script or by a simple C program. Such generated source code would even work with any ANSI C compiler.

@iskunk
Copy link
Author

iskunk commented Oct 30, 2019

Have you looked at that first Stack Overflow link I posted ("Is it a conforming compiler extension to treat non-constexpr standard library functions as constexpr?")? Not only does it appear that the standard math-library functions are explicitly not allowed to be constexpr, it seems there is some possibility that G++'s non-conformance on this point may be dialed back in the future (though that discussion dates from 2014, so perhaps it has been superseded).

Given that at present, G++ builds are the only known beneficiaries of this optimization, I believe taking the "classic" approach of generating the table via an external script (and sidestepping the whole C++ standards-conformance issue) would be a good way to go. In my experience, it's certainly possible for code to be too modern for its own good...

@stweil
Copy link
Member

stweil commented Oct 30, 2019

The code for the classic approach is much shorter than the whole discussion here, even shorter than your comment. I'll send a pull request for it tomorrow.

@iskunk
Copy link
Author

iskunk commented Oct 30, 2019

I'm not surprised that the discussion outweighs the code :-]

Thank you Stefan, I appreciate this solution which will allow all modern C++ compilers to benefit from the optimization.

@amitdo
Copy link
Collaborator

amitdo commented Sep 9, 2021

FYI, Intel C/C++ compilers complete adoption of LLVM

I know It does not matter for this issue because the code in #2740 is compiler independent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants