How to set only the overflow flag ARM assembly?
Asked Answered
A

4

5

I have been messing around with the Flags while learning ARM assembly on my Raspberry PI. I have devised of ways only to set the zero flag, only the negative, and only the carry flag. However I can't think of a way to set only the overflow flag. Is it possible? Any help would be appreciated!

The challenge is not to write to the cpsr (as I am not allowed to for various reasons, otherwise that would be the best solution, because it is the best solution)

Edit: only setting the overflow flag with all the others zero/clear. Using only arithmetic or shifting. NZCV = 0001

Edit2: To clarify further, I would think multiple instructions would be needed to achieve this.

Alfonsoalfonzo answered 2/10, 2018 at 3:15 Comment(7)
Do you mean with an 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
@Peter Cordes Yes for the hex but’s would go far as to say any arithmetic or shifting.Alfonsoalfonzo
@PeterCordes ARM does not invert the carry out like other architectures so you get a signed and unsigned overflow at the same time. Carry out is not a borrow on ARMWadding
Hmm which ARM? I would assume that arm would not change that kind of thing with aarch64.Wadding
I am thinking not but you can try an experiment if you like.Wadding
Are you limited to a single instruction or can you use as many as you want?Wadding
are you allowed to simply write to the psr?Wadding
A
4

I don't see an obvious way with just one instruction, but you could do with combination. For example:

mov  r0, #0x80000000
mov  r1, #0x00000001
subs r2, r0, r1  ; C and V set
mov  r3, #0x10
asrs r3, #1      ; C cleared, V not changed
Alexine answered 2/10, 2018 at 8:8 Comment(1)
You could tighten that up if you wanted to, e.g. mov r1, #0x10 / subs r2, r0, r1, lsr #4 to reuse one constant. Or maybe use the barrel shifter so you don't need two separate inputs regs to start with either. Like mov r0, #0x80000002 should be encodeable with a rotated 8-bit constant, and adds r0, r0, r0 produces r0 = 4 with C and V set, ready for an asrs.Mental
W
2
abc cr
000 00 
001 01  x
010 01 
011 10 
100 01 
101 10 
110 10  x
111 11 

Signed overflow is when carry out is not equal to carry in. If the first columns are msbits of operand a b and carry in to the msbit (other bits don't matter for signed nor unsigned overflow), the right columns are carry out and result. If the result is 1 then you get the N bit. So it has to be with the msbits of the operands being 1 and the carry in being a 0

0xxx (carrys)
1xxx (operand a)
1xxx (operand b)

0x80 + 0x80 = 0x00 (zero flag)
0x81 + 0x81 = 0x02 (need some other ones)

  100000010
   10000001
+  10000001
============
   00000010

-127 + -127 = -254 the largest negative you can get is -128, 0x80, so this is a signed overflow.

But there is a carry out, isn't there?

So maybe a subtract will work -127 - 127

  100000011
   10000001
+  10000000
============
   00000010

But being a subtract does it invert the carry out to a borrow leaving a 0 in the carry bit? That is not how ARM works, other processors/cores will do this.

So in order to be able to do this you need a processor that defines the carry out as a borrow for a subtract (inverts the carry out at the end of the addition).

You edited your question while writing this, how would a shift operation modify a signed overflow? Needs to be add or subtract (needs to use an adder).

Wadding answered 2/10, 2018 at 5:17 Comment(1)
Note that signed overflow is when carry out is not equal to carry in for the sign bit (the MSB), not for the computation as a whole.Staircase
H
2

I'm relatively new to assembly and while experimenting and researching I have found the following way to set a single flag. Note that, I'm using ARM7TDMI-S based 32-bit RISC Microcontroller architecture. There are so-called MRS and MSR instructions. MRS is used to read the flags and MSR is used to write the flags.


Here is how I set each flag:

msr cpsr_cxsf, #0x80000000 ; N Flag
    
msr cpsr_cxsf, #0x40000000 ; Z flag
    
msr cpsr_cxsf, #0x20000000 ; C Flag
    
msr cpsr_cxsf, #0x10000000 ; V Flag
Hurds answered 19/10, 2021 at 15:15 Comment(0)
M
0

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.

Mental answered 25/11, 2023 at 23:58 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.