AARCH64 NEON 非正規化数制御のメモ

## 背景

https://ja.wikipedia.org/wiki/%E9%9D%9E%E6%AD%A3%E8%A6%8F%E5%8C%96%E6%95%B0

レイトレーシングではあんまり出てこない気がする非正規化数ですが, embree では非正規化数 off を推奨しているため, ARM NEON の場合どうなるのか気になって調べました.

## 参考: x85 SSE の場合

以下のように設定できる(default は off = 非正規化数有効).

```
           #include "xmmintrin.h"
           #include "pmmintrin.h"

           _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); // FTZ
           _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON); // DAZ
```

FTZ: Flash to zero. 演算結果が denormal になったらゼロにする(SSE から)
DAZ: Denormal as zero. 入力が denormal だったらゼロにする(SSE3 から)

## NEON の場合

aarch64 アーキテクチャ(arm64v8)を想定します.

FPU 制御レジスタでふるまいを設定できるが...

https://developer.arm.com/documentation/dui0204/j/neon-and-vfp-programming/neon-and-vfp-system-registers/fpscr--the-floating-point-status-and-control-register

https://developer.arm.com/documentation/den0024/a/aarch64-floating-point-and-neon/new-features-for-neon-and-floating-point-in-aarch64

flush-to-zero: aarch32 NEON では常に有効(レジスタの設定は無視される), aarch64 ではレジスタ制御でいけるっぽい?

denormal as zero は, 明記は無い.

## SSE2

```
#include "xmmintrin.h" // FTZ(SSE)
#include <cstdint>
#include <cstdio>
#include "pmmintrin.h" // DAZ(SSE3)

struct FP32
{
  union {
    float f;
    uint32_t ui;
  };
};

int main(int argc, char **argv)
{
  _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); // FTZ
  _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON); // DAZ

  __m128 a = _mm_set1_ps(1.0f / 3.0f);
  __m128 b = _mm_set1_ps(1.17e-38f);

  __m128 c = _mm_mul_ps(a, b);

  float buf[4];

  _mm_storeu_ps(buf, c);

  FP32 fp;
  fp.f = buf[0];

  printf("ret = %g(0x%08x)\n", buf[0], fp.ui);

  return 0;
}
```

1.17e-38f は 2^(-126) = minimal normal よりちょっと大きな数です.
掛け算したら非正規化数になるようにします.

FTZ, DAZ off :  ret = 3.9e-39(0x002a779d)
FTZ on : ret = 0(0x00000000)
DAZ on : re = 0(0x00000000)
FTZ, DAZ on : re = 0(0x00000000)

DAZ を有効にすると, store 命令にも影響があるのか, 今回ではゼロになりました. bit pattern もゼロです.

## AARCH64 NEON

```
#include <cstdint>
#include <cstdio>
#include <cmath>

#include <arm_neon.h>

struct FP32
{
  union {
    float f;
    uint32_t ui;
  };
};

int main(int argc, char **argv)
{
  uint64_t reg = __builtin_aarch64_get_fpcr();
  __builtin_aarch64_set_fpcr(reg | (1ull << 24)); // 24bit: flush-to-zero

  float32x4_t a = vdupq_n_f32(1.0f / 3.0f);
  float32x4_t b = vdupq_n_f32(1.17e-38f);

  float32x4_t c = vmulq_f32(a, b);
  c = vmulq_f32(c, a);

  __attribute__((aligned(16))) float buf[4];

  vst1q_f32(buf, c);

  FP32 fp;
  fp.f = buf[0];

  printf("ret = %g(0x%08x)\n", buf[0], fp.ui);
  printf("FP_SUBNORMAL = %d\n", std::fpclassify(buf[0]) == FP_SUBNORMAL);

  return 0;
}
```

https://blog.miz-ar.info/2020/01/fp-rounding-mode/

を参考にして, builtin 命令で浮動小数点数制御レジスタを設定します.
flush-to-zero は 24bit 目になります.

FPCR 制御する builtin 命令は clang は対応していないので, ここでは gcc を使います.
(clang の場合だと assembler で設定になるかしら)

Jetson AGX(aarch64 Ubuntu 18.04 linux)で試しました.

### FTZ off

ret = 1.3e-39(0x000e27df)
FP_SUBNORMAL = 1

### FTZ on

ret = 0(0x000e27df)
FP_SUBNORMAL = 0

ゼロになりました(FP_SUBNORMAL にも分類されない)が, **ビットパターン自体はゼロにはなりませんでした**. 
また, 非正規化数を入力(load)し, store -> 表示も同様のふるまいでした.

何かしら denormal 自体は計算していて, HW 側で FPCR を見てゼロとみなすかどうかマスクをかけている感があります. 

AARCH64 NEON では, flush-to-zero は SSE でいう FTZ + DAZ 両方を対応しているときと同じふるまいと考えてよさそうです.

### まとめ

非正規化数は aarch32(32bit) NEON ではサポートされない
aarch64 では非正規化数対応している. denormal as zero だけのモードは無い模様.

SSE で FTZ + DAZ on に設定している場合は, AARCH64 NEON でも flush-to-zero で同様の振る舞いにすることができる.

AARCH64 NEON では, 非正規化数かどうかの判定を, bit pattern で判定するのは避けるのがよさそう.