Every asm instruction in your source maps to at 1 machine instruction1. Your assembler is not a compiler.
What you can do in one instruction depends completely on what the machine code can do; asm source syntax can express everything. The x86 cmp
instruction can only read 2 input operands and write flags, so of course it can't set flags based on 6 inputs.
Sometimes you can efficiently turn a C source boolean expression into one asm branch, e.g. logical ||
can use a bitwise OR, which sets ZF based on the result being non-zero. So if(a || b || c)
could compile to
or eax, ebx
or eax, ecx
jnz .not_if
; if body
.not_if:
But more typically it's best to actually compare-and-branch on each part of the condition separately. e.g. if (a && b)
might compile to:
test eax,eax
jz .not_if
test ebx,ebx
jz .not_if
; if body
.not_if:
You can't test eax,ebx
because bitwise AND can be 0 with EAX=1 and EBX=2, for example.
Another case you can combine things into one cmp/jcc is range-checks: double condition checking in assembly. sub
to bring one end of the range to 0
, then cmp
/ ja
(unsigned compare). Jumps if the sub
wrapped to a large unsigned value, or if the value was out of range high.
More generally, look at what compilers do when compiling C. Assignment to a volatile
variable is a convenient thing to put in an if()
body if you don't want the compiler to be able to turn the if
into a branchless CMOV or something. See How to remove "noise" from GCC/clang assembly output? for more about looking at compiler output and constructing useful compiler input. Especially Matt Godbolt's CppCon talk.
Actually creating booleans in integer registers and ANDing / ORing them together works on MIPS (which doesn't have a FLAGS register, so you either branch or compare into an integer register).
Or on POWER / PowerPC where there are multiple condition-code fields and you can compare into CR0, compare into CR1, then use a condition-register instruction to combine the conditions.
IDK if you forgot to tag x86, or if you were trying to ask a generic question about different assembly languages.
Footnote 1: Many RISC architectures have "pseudo-instructions" that expand to 2 or 3 actual machine instructions, like li $t0, 0x1234567
on MIPS -> lui
to set up the upper half of the 32-bit constant, and ori $t0, $t0, 0x4567
to set the low half. Fixed-instruction-width architectures can't fit an arbitrary 32-bit constant into one instruction the way x86 can.
x86 doesn't use "pseudo-instructions". They're already complex enough. :P