Jump to content
  • Advertisement
Sign in to follow this  

Dependable cmov in Visual C++

This topic is 3801 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Also, this worked in Visual Studio 2008... I will assume it works in 2005 as well but anyone who can testify to this is encouraged to let me know. Anyway, I have googled and searched gamedev occasionally to find a way to get cmov instructions consistently, but have not found a single answer that works every time. Closest is "use ? :". After doing so, I've still had problems getting it to EVER happen. Anyway, I took another stab and here's my results for anyone interested. As far as project configuration options go, SSE has to be enabled, and any form of the overall Optimizations need to be turned on (/O1 /O2 /Ox). Whether you favor size, speed, or neight (/Ot, /Os, none) is irrelavent. Also, this is for code generated for x86, and is only dependable with 32 bit integers, although sometimes works with __int64's for some reason. Essentially, it will generate a cmov with a single conditional and 2 variables, "X = Cond ? X : Y" or "X = Cond ? Y : X". Expressions and constants of any kind will NOT work in place of the variables, but the conditional can be anything, as long is it is only 1 conditional. Another stipulation I found, is that the compiler needs to think the variable ALREADY has something assigned to it, even if that assignment was undefined. For an example:
const int ConstVar;  // int ConstVar = 5 would be equivalent if it doesn't change.
int Condition;
int Var;
int InitVal;
int Result = InitVal;  // If this was just 'int Result;' than it wouldn't always work

Result = Condition ? Result : Var;  // Works every time
if (Condition) Result = Var;  // Also works every time

Result = Condition ? InitVal : Var;  // Only sometimes (suprisingly)
Result = Condition ? Result : ConstVar;  // Doesn't!  Usually turns into a jump!
Result = (Condition && Condition2) ? Result : Var;  // Doesn't!  Only 1 conditional seems to work.
Result = Condition ? Result : 10;  // Just like a const var of course
Result = Condition ? Var : ConstVar;  // Nope!
Result = Condition ? Result : Var / 5;  // Nope!
So a conditional statement, containing only 1 comparison (an expression like 'Var1 || Var2' is 2 comparisons of course where 'Var1 | Var2 / 10 + (Var3 - 6) > Var2 * Var3' isn't), followed by assigning a single variable (not an expression!) to a single result variable, and the original variable to itself otherwise, results in a cmov every time. Anything else might work, but I have found it to be either inconsistent, or not produce it at all. Also, arrays are a bit trickier.
// Works consistently
for (int Index = 0; Index < 10; Index++) {
  long In1 = rand();
  long SomeResult = rand();
  long Out = OutBuffer[Index];  // OutBuffer doesn't have to be volatile
  Out = In1 < InBuffer[Index] ? Out : SomeResult;
  OutBuffer[Index] = Out;  // Notice we did not use an array directly in the resulting assignment
You CANNOT use an array in the assignment at the end, directly. If SomeResult, in this case, was assigned an array entry called InBufferTwo[Index], InBufferTwo would have to be declared as volatile to work!!!! Otherwise the compiler will optimize it into the conditional, skipping using 'SomeResult', and not recognize it as a cmovable operation, sadly to say. It also doesn't like pointer dereferencing in place of a straight array lookup (in the conditional). So either assign the dereferenced pointer to another var, or use a direct array lookup [] style, or just assign all your array values to an integer variable! Very interesting the confinements to get cmov ops to generate. If anyone has anything to add, it'd be great... it seems like I'm not the only one that has tried to find a dependable solution to this problem. And of course, always check the assembly output to make sure it worked! Cheers -Scott [Edited by - popsoftheyear on July 14, 2008 3:48:20 PM]

Share this post

Link to post
Share on other sites
It still compiles to a cmov. However, all this only consistently works for 32 bit integers (compiling for x86). 64 bit integers work occasionally.

SSE seems to be the only configuration option that affects it, along with any optimization level besides "Disabled" turned on.

[Edited by - popsoftheyear on July 14, 2008 1:59:44 PM]

Share this post

Link to post
Share on other sites
Additional information added to original post. Compiler options, as well as how to use with arrays. Let me know if anything else breaks the consistency factor in this! I hope someone finds this useful.


Share this post

Link to post
Share on other sites
And I present one more snippet, only because this can get complicated.

for (int XPos = LI; XPos <= RI; XPos++) {
// Test against z buffer and draw
volatile unsigned int Result1 = ((RVal+DeltaR*D) & 0xff0000) | (((GVal+DeltaG*D) & 0xff0000) >> 8)
| ((BVal+DeltaB*D) >> 16);
unsigned int Res2 = Result1;

unsigned int Out = Buf[D];
unsigned int In = *ZBuf;

//No - Doesn't like pointer dereferencing
// Out = *ZBuf > ZVal ? Res2 : Out;

//No - Result1 is volatile so this code doesn't optimize, and if it wasn't volatile the optimizer
// would say "hey, there is too much going on with Result1 up there, we'll throw a conditional and skip
// IT ALL if 'In > ZVal' is false...
// Out = In > ZVal ? Result1 : Out;

// Yes - No pointer dereferencing IN the conditional. You can optimize statements containing Res2,
// and no array lookups directly in either assignment after the conditional. Variable assigns to self
// on one of the assignments. No expressions involved in the assignments. This is the safest, best
// way to guarantee a cmov, although perhaps the compiler was right about skipping all that Result1
// math....
Out = In > ZVal ? Res2 : Out;
Buf[D] = Out;

// Writing 'Out = In' on the next line, while equivalent, would destroy BOTH cmov compilations
// below and above. The compiler needs some pretty specific context clues, but they always seem
// to work once you get them down.
Out = *ZBuf;
Out = Out > ZVal ? ZVal : Out;
*ZBuf = Out;

Share this post

Link to post
Share on other sites
Would it be possible to write an inlineable function that would then work every time, assuming correct optimisation settings? Building your own intrinsic function in a way?

Share this post

Link to post
Share on other sites
From what I can tell that's actually more difficult. You still have to follow the same rules for the variables you send to it, even if you do some of the same stuff inside the inlined function, and even then it doesn't consistently work... it's a lot more dependant on what is around the function. I've found that seemingly unrelated statements around it affects it, sometimes.

Now while this might be considered a flaw to some, and it's such a pain to get cmovs to generate in VC++, here are a couple articles that are worth noting:
One by Linus Torvalds and one by a couple people who read the previous article.

It seems that Visual Studio will use cmov in specific cases, and you can trick it into thinking it is the best solution (and it may very well be sometimes), but it is definitely not even worth pursuing if the branch is predictable.

Another good point is what I ran into earlier... how it may be more costly to cmov if the cmp, jxx set would have skipped more than a simple assignment. With the cmov you might be unnecessarily calculating a lot of things.

Interesting stuff...


Share this post

Link to post
Share on other sites
Sign in to follow this  

  • Advertisement

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!