3

I'm trying to coerce the pre-processor to perform some math for me so a constant gets propagated into inline assembly. Here's the reduced case:

inline
unsigned int RotateRight(unsigned char value, unsigned int amount)
{
    COMPILE_ASSERT(((unsigned char)(amount%32)) < 32);
    __asm__ ("rorb %1, %0" : "+mq" (value) : "I" ((unsigned char)(amount%32)));
    return value;
}

The code above relies upon CPU specific functionality, and I'm OK with it (its actually a template specialization on x86/x64 Linux when GCC is available). The "I" constraint says the integral value must be between [0,31] inclusive.

Callers of the code would look like:

byte b1 = RotateRight(1, 1);
byte b2 = RotateRight(1, 31);

A RotateRight(1, 31) comes from the cryptographers (its undefined behavior in C/C++ because a byte can only be rotated in the range [0,7]). I can break free from C/C++ constraints using ASM. And since the shift amount is known at compile time, I'd like it to be reduced at compile time; and I'd like the rorb version using the immediate-8 generated.

Without the COMPILE_ASSERT, the code compiles but I'm not sure if the constant is being propagated. That is, it might be generated with an unexpected reduction (% 32). With the COMPILE_ASSERT, the code fails to compile.

$ make validat1.o
g++ -DNDEBUG -g2 -O3 -march=native -pipe -c validat1.cpp
In file included from simple.h:10:0,
                 from filters.h:6,
                 from files.h:5,
                 from validat1.cpp:6:
misc.h: In function ‘T CryptoPP::rotlFixed(T, unsigned int) [with T = unsigned char]’:
misc.h:940:43: error: ‘y’ cannot appear in a constant-expression
  CRYPTOPP_COMPILE_ASSERT(((unsigned char)(y%32)) < 32);
                                           ^
misc.h:72:85: note: in definition of macro ‘CRYPTOPP_COMPILE_ASSERT_INSTANCE’
 _COMPILE_ASSERT_INSTANCE(assertion, instance) static CompileAssert<(assertion)>

I know I'm not supposed to use a #define, and C++ inline functions are the answer. But I feel like I'm suffering a disconnect.

How do I force the compiler to propagate the value that involves const values?

Or, if the COMPILE_ASSERT is the wrong tool (const is being propagated), how do I set up a test so that I can verify the immediate-8 version of the rorb is used?


Related, this is a C++03 project. It does not use Boost, does not use Cmake, does not use Autotools, etc.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
jww
  • 97,681
  • 90
  • 411
  • 885
  • The shift amount is limited in C and C++ because the hardware masks the shift count. Using assembly doesn't change that. – Bo Persson Jul 19 '15 at 06:57
  • @Bo - agreed, but I'm not bound by C/C++ undefined behavior. If I reduce the constant at compile time or I place the shift amount in CX, then I get the reduction to the low order 5 bits for free. And its well defined in ASM, and not subject to removal like in C/C++. – jww Jul 19 '15 at 07:05
  • My MinGW compiler says *x* is undefined (at return statement). Is it OK? – stgatilov Jul 19 '15 at 07:06
  • Also, isn't *amount%32* always less than 32 for unsigned *amount*? – stgatilov Jul 19 '15 at 07:14
  • @stgatilov - yes, it always should be. But I don't know how to construct a test to ensure the reduction is occurring at compile time. Hence the reason I asked: *"...how do I set up a test so that I can verify the immediate-8 version of the `rorb` is used?"* – jww Jul 19 '15 at 07:17
  • It seems to me that expression with compile-time constant arguments should be compile-time constant automatically. Nevertheless, you can use static const as in my updated answer if you are not yet sure. – stgatilov Jul 19 '15 at 07:25
  • Try this: `int main(int argc, char *argv[]) {char c = 9; asm("rorb %1, %0" : "+mq" (c) : "I" (argc)); return c;}` Since 'I' requires a constant, this gets a compile error (impossible constraint). Changing `(argc)` to `(3)` compiles correctly. IOW, if somehow during the compile+inlining of your RotateRight amount is not a constant, the compile will fail. (FYI: If somehow RotateRight isn't inlined, it will also fail). So, will `rorb $, ` always get assembled to immediate-8? Can't think why it wouldn't. It's not like the assembler is going to add a `mov cx`. – David Wohlferd Jul 19 '15 at 08:00

1 Answers1

4

When you specify amount as function argument, you lose its compile-time constness.

Why don't you declare amount is template argument? In such case the function user is also forced to pass a compile-time constant, which is good too.

To ensure that shift is used as compile-time constant, you can create a static const local variable.

template<unsigned int amount> inline
unsigned int RotateRight(unsigned char value)
{
    static const unsigned char shift = (unsigned char)(amount%32);
    __asm__ ("rorb %1, %0" : "+mq" (value) : "I" (shift));
    return value;
}
stgatilov
  • 5,333
  • 31
  • 54
  • Oh, that's a good idea. Let me try it. I'm working on another problem at the moment, so I need some time to change the source code. – jww Jul 19 '15 at 07:06
  • So I looked at the impact of the changes by adding an additional template argument. (Currently, `RotateLeft` and `RotateRight` is a template based on word size). I don't think I am going to be able to make the change. I'll tuck it away for a future change, like a major version upgrade of the library. – jww Jul 19 '15 at 08:26
  • @jww Any specific reason? In C++03 there is no constexpr, so if you pass *amount* as function argument, the language will always treat it as not a compile-time constant, and you are left on the mercy of the optimizer (which is never guaranteed). – stgatilov Jul 19 '15 at 08:31
  • *"Any specific reason?"* - its a public library ([Crypto++](http://www.cryptopp.com/)), and it would be a breaking ABI change. Our governance allows us to move an ABI forward with a minor increment (i.e., 5.6.2 to 5.7). "Moving forward" means *not* breaking existing user code. But to completely break the ABI, we need a major version bump (i.e., 5.6.2 to 6.0). We are not ready for 6.0 just yet. – jww Jul 19 '15 at 08:45
  • Regarding making it a template parameter, checkout the comment at [LLVM Bug 24226 - Constant not propagated into inline assembly, results in "constraint 'I' expects an integer constant expression"](https://llvm.org/bugs/show_bug.cgi?id=24226): *"I've been bitten by this in the past. IIRC, in the past when I ran into this, even a constexpr expression or a template argument didn't help"*. – jww Jul 23 '15 at 05:22