I think there's no way to get this V=1 C=0 with a positive result (Z=0 N=0) from a single arithmetic instruction on ARM that writes all four flags, regardless of register inputs. So @domen's answer using a shift to clear C and other flags while leaving V unmodified might be the best we can do other than msr
to simply set CPSR directly.
Arithmetic in general is allowed in the question, but mul
leaves V unmodified, and mls
/mla
/smull
and friends don't set flags. Shifts like lsl
also leave V unmodified, like bitwise instructions such as and
. Overflowing sdiv
of INT_MIN/-1
doesn't leave any trace: sdiv
doesn't set flags at all.
Addition and subtraction set all four flags, but subtraction sets C as a not-borrow output if you look at it as actual binary subtraction. (ARM subtraction of x - y
sets the Carry flag as if from add-with-carry with x + ~y
with carry-in = 1.)
- We need the result to be non-zero so Zero is clear
- We need the result to be non-negative so Negative is clear, thus we need the result to be positive.
- We need Carry to be clear, so we can't add
negative + negative => positive
; their sign bits would carry out into C. And positive+positive
overflowing to negative would set N.
So we can't use addition.
Subtraction can signed-overflow with positive-negative => negative
, which we don't want.
Or with negative-positive => positive
. But negative numbers are unsigned-higher than positive numbers so these subtractions don't produce a borrow. Thus they do set C. (bhi
branches when C is set and Z is clear.)
So neither overflow condition for subtraction can avoid setting C. (At least without considering adc
/ sbc
, not sure if those help.)
Here's one way that's fairly compact and efficient: start with a subs
that sets flags the way we want except for N
, then a shift that overwrites all the flags other than V
.
.syntax unified
movs r0, #0x80 // fits in a Thumb 16-bit mov reg, imm8 (zero-extended)
subs r0, r0, r0, lsl #24 // NZCV=1001 from 0x80 - INT_MIN (0x80000000) overflowing to negative result 0x80000080
lsrs r0, #1 // R0 = 0x40000040, NZCV=0001 (checked with QEMU + LLDB)
In Thumb-2 machine code, 2 of the instructions are 16-bit. I haven't thought of a way to make all 3 instructions have 16-bit Thumb encodings, with this or any other strategy.
0: 2080 movs r0, #0x80
2: ebb0 6000 subs.w r0, r0, r0, lsl #24
6: 0840 lsrs r0, r0, #0x1
With borrow/carry inputs to sbc
/adc
? No, doesn't help
0 - (1<<31) - 1
is 0x7fffffff
, which is like 0 - INT_MIN - 1
. Doing that as all one operation is like 0 - (INT_MIN + 1)
, subtracting a negative from 0 and getting a positive without overflow. Doing that with ARM instructions like rscs r0, #0
clears both C and V flags.
0 - 0x7fffffff - 1
produces 0x80000000
= INT_MIN, so it's not even signed overflow. Subtracting from a positive won't either, but subtracting from a negative number would. But negative numbers are unsigned-higher than positive numbers, so the subtraction wouldn't have a borrow, so -3 - 0x7fffffff - 1
makes C=0. And we didn't need sbc
if we start with negative numbers lower than -1
, so that's not helping.
0 + 0x80000000 + 1
with adc
doesn't help, that's INT_MIN + 1
which doesn't have signed overflow and is negative. Any signed overflow involving addition either has carry or a negative result, even with adc
.
So none of adc
, sbc
, or ARM-mode-only rsc
help here.
add
instruction or something that leaves all the other flags cleared?0x80000000 - 1
should set V without setting any other flags, wrapping from INT_MIN to INT_MAX. – Mental