10

I'm not very familiar with the internal details of ARM processors, but I do not understand the following behaviour on my Nvidia Jetson Nano dev board.

C code sample ...

//main.c

#include <stdio.h>

int main()
{
    int fred = 123;
    int i;

    for(i = -10 ; i <= 10 ; i++)
        printf("%d / %d == %d\n", fred, i, fred / i);

    return 0;
}

Compiled with :

gcc main.c -ggdb

Running the resulting a.out executable yields the following output...

123 / -10 == -12
123 / -9 == -13
123 / -8 == -15
123 / -7 == -17
123 / -6 == -20
123 / -5 == -24
123 / -4 == -30
123 / -3 == -41
123 / -2 == -61
123 / -1 == -123
123 / 0 == 0                  //unexpected!
123 / 1 == 123
123 / 2 == 61
123 / 3 == 41
123 / 4 == 30
123 / 5 == 24
123 / 6 == 20
123 / 7 == 17
123 / 8 == 15
123 / 9 == 13
123 / 10 == 12

The exact same code compiled on an ancient Pentium 4 using gcc 3.7 causes (as expected) a runtime exception to be thrown when i reaches 0 and causes a division by zero.

The Nvidia board is running Ubuntu 18.04 LTS, gcc version 7.4.0 (latest) and in every other respect runs beautifully. I have also compiled the equivalent Ada language version of this code and a runtime exception is raised as one would expect (because Ada does safety checks ahead of time on my behalf).

I realise that in C, "division by zero yields undefined behaviour" is likely the explanation for this, but for two versions of the same compiler suite to give such different results to the same operation is puzzling to me.

What circumstances could cause an Nvidia Tegra ARM (64 bit) CPU to allow a division by zero to pass by unnoticed by the OS?

EDIT: Details about the CPU from /etc/cpuinfo...

$ cat /proc/cpuinfo
processor       : 0
model name      : ARMv8 Processor rev 1 (v8l)
BogoMIPS        : 38.40
Features        : fp asimd evtstrm aes pmull sha1 sha2 crc32
CPU implementer : 0x41
CPU architecture: 8
CPU variant     : 0x1
CPU part        : 0xd07
CPU revision    : 1

.... truncated ....
klutt
  • 30,332
  • 17
  • 55
  • 95
  • 1
    Can you check if `FPSCR->DZE` is set. It is the `Division by Zero exception enable bit`. It may apply to integers also. – Fiddling Bits Sep 11 '19 at 17:34
  • @FiddlingBits It seem to be running under Linux (userspace I presume?). So I guess it's about SIGTRAP handling. Well, maybe the kernel is not configured to set this bit though... – Eugene Sh. Sep 11 '19 at 17:35
  • @EugeneSh. Yes userspace, running as non-root user with no special privileges. –  Sep 11 '19 at 17:38
  • 2
    Related question: https://stackoverflow.com/questions/37262572/on-which-platforms-does-integer-divide-by-zero-trigger-a-floating-point-exceptio – Eugene Sh. Sep 11 '19 at 17:40
  • Since it is undefined behavior, compiler is not doing anything, and generates code to perform division. Absence of signal means that either it is your kernel which masks it, or hardware doesn't generate trap. Did you check hardware spec? – SergeyA Sep 11 '19 at 17:42
  • @FiddlingBits, looks like I need to read bit 9 of the FPSCR register. Is there a simple way to do that in C or will it involve inline ASM? –  Sep 11 '19 at 17:56
  • 2
    What ARM architecture is it? Based on this: https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/divide-and-conquer there might be different behavior in case of division by zero. For example on ARMv7-A - divide by zero always returns a zero result. – Alex Lop. Sep 11 '19 at 17:58
  • @AlexLop. I've added the output of /etc/cpuinfo to the end of the Question. "A57" is ARMv8 silicon. –  Sep 11 '19 at 18:02

2 Answers2

10

Nvidia Jetson Nano dev board uses ARM Cortex-A57 (Link) which is based on ARMv8 architecture. Based on the instruction set spec of ARMv8, integer division by zero returns zero and not trapped.

2.3 Divide instructions

ARMv8-A supports signed and unsigned division of 32-bit and 64-bit sized values.

Instruction Description

SDIV Signed divide

UDIV Unsigned divide

...

Overflow and divide-by-zero are not trapped:

• Any integer division by zero returns zero

So the compiler generates sdiv in this case (see example) and the CPU returns 0 without any exception. When you compile the same code on different platforms, each other CPU may reacts differently to division by zero. As you mentioned in your question, in case of division by 0, the behavior is undefined by the C standard.

Community
  • 1
  • 1
Alex Lop.
  • 6,810
  • 1
  • 26
  • 45
  • I never realised before that such things were handled so differently by various architectures. I had foolishly assumed that such a fundamental thing would always result in a hard SIGFPE, as POSIX demands. Fascinating. Thank you for your efforts. –  Sep 11 '19 at 18:20
  • @Wossname Can you share where POSIX *requires* that integer division by zero raises `SIGFPE`? What I found is [this](https://pubs.opengroup.org/onlinepubs/9699919799/) that suggests raising `SIGFPE` with a value of `FPE_INTDIV` is optional: "Implementations may [...] contain extensions or limitations that prevent some values from being generated." – njuffa Sep 12 '19 at 17:40
0

It's worth noting that not all ARM processors are the same (as has been noted). I am working on a project with an STM32L4 (which contains a cortex-m4). It does have the SDIV and UDIV instructions. However, the generation of an exception on division by zero is configurable (see the STM32 Cortex®-M4 MCUs and MPUs programming manual, page 231)

Further complicating things is that occasionally, the compiler can recognize the division by zero case and generate illegal instructions (obviating the configuration I noted). Have a play with this example.

georgn
  • 21
  • 1
  • 3